IMPORTANT: For Figma design-to-code work, use only the figma-cost-optimizer-bridge MCP server. Do not use or fall back to the official Figma MCP / figma-mcp directly.
This document defines the rules for all coding agents working inside ditto-fe-migration/, including Claude Code and Codex.
Repository-level rules must be read from ../CLAUDE.md first.
Codex must read ditto-fe-migration/AGENTS.md, which points back to this document as the FE source of truth.
This FE project is developed on the feat/s3-migration branch and is deployed statically through S3/CloudFront using output: 'export' in next.config.ts.
The project is gradually migrating from legacy API code in src/lib/api to OpenAPI codegen services in src/shared/lib/api/generated.
Only this project is currently in scope:
ditto-fe-migration/
Out of scope:
ditto-develop/ditto-be/ditto-fe/
Do not modify out-of-scope projects unless the user explicitly asks.
This repository uses a three-step AI workflow:
- Claude Code plans the task.
- Codex implements the approved plan.
- Claude Code reviews the diff and prepares or validates the PR.
Claude Code should usually operate in planning/review mode.
Codex should usually operate in implementation mode.
When asked to plan, Claude must not edit files.
When asked to review, Claude must inspect the diff and identify:
- Blocking issues
- Non-blocking suggestions
- Validation gaps
- PR risks
- Follow-up prompts for Codex if needed
-
Preserve behavior during cleanup.
- Do not change business logic during cleanup.
- If behavior change seems necessary, stop and ask the user.
-
Keep one source of truth.
- The same concept should exist in one canonical place only.
-
Search before writing new code.
- Use
rgor equivalent to find existing implementations. - Reuse existing utilities and patterns.
- Use
-
Keep PRs small.
- Work in reviewable batches.
- Separate file rename commits from content-change commits.
-
Treat public API changes as high-risk.
- Ask the user before changing externally imported symbol names or signatures.
| Area | Canonical Path | Rule |
|---|---|---|
API client (live, /api/v1) |
src/shared/lib/api/externalApi.ts (+ externalClient.ts) |
Forward-canonical client for the live BE. Matching/profile/settings/auth run here. Add new endpoints following the existing pattern. |
API client (generated, /api) |
src/shared/lib/api/generated/ (via client.ts) |
OpenAPI codegen services still used for chat/quiz/home. Migrated to /api/v1 incrementally. Legacy src/lib/api already removed. |
| Fetch wrapper | src/shared/lib/api/client.ts |
Consolidate duplicated apiFetch logic. Admin-only logic may live in adminClient.ts. |
| Design system UI | src/shared/ui/ |
Button, Text, Avatar, Modal, Toast, and other reusable UI components. |
| Page-specific components | src/components/<domain>/ |
home/onboarding/quiz-specific components. Promote only truly reusable components to shared/ui. |
| Feature hooks/types/containers | src/features/<domain>/ |
matching/profile/conversation feature logic. Do not cross domain boundaries casually. |
| Global state | src/context/ |
HomeReady and Toast. Check existing context before adding new context. |
| Design tokens | src/styles/token/{atomic,semantic,components}.css |
The only CSS token source. |
| Runtime token references | src/shared/styles/tokens.ts |
TS runtime references only. Must stay synchronized with CSS tokens. |
| Global types | src/types/ |
Feature-specific types belong in features/<domain>/model/types.ts. Reuse generated DTOs where possible. |
externalApi.ts (/api/v1) is the main, surviving client. The generated client is being phased out: as the live /api/v1 BE is built out, the generated services (and client.ts) will be deleted. Do not deepen the generated client — route new work through externalApi.
The project currently uses two shared API clients. Every request goes through one of them — never call fetch or axios directly outside the client modules themselves.
src/shared/lib/api/externalApi.ts(+externalClient.ts): the canonical client for the live/api/v1BE. Matching, profile, settings, and auth already run through it. Add newly-migrated endpoints here following the existing function pattern, and reuse the DTO types defined alongside them.src/shared/lib/api/generated/(viaclient.ts): OpenAPI codegen services for the/apiendpoints not yet migrated (chat, quiz, parts of home). Regenerate withnpm run generate-clientagainstditto-api.json; never hand-edit generated files. These migrate to/api/v1incrementally and the generated layer is removed once empty.- Token access for both clients goes through the single entry point
src/shared/lib/auth.ts(getAccessToken/setTokens/clearTokens). Do not re-readlocalStorageforaccessTokendirectly in client code. - Legacy
src/lib/apihas been removed. Do not reintroduce it. - Admin token handling stays separate in
adminClient.ts. - Reuse generated / externalApi DTOs. Do not manually duplicate them, and do not make response fields optional just to silence type errors.
- React Query is outside the current cleanup scope. Do not introduce React Query.
- When generated and live (
/api/v1) specs disagree, stop and ask (§15) — do not guess.
- Reusable components belong in
src/shared/ui. src/components/common,src/components/display, andsrc/components/inputare transitional zones.- Common components in transitional zones should gradually move to
shared/ui. - Page/domain-specific components must not be moved to
shared/ui. - Toast must be called only through the
useToast()hook. - Do not directly import and render the
Toastcomponent. - Use PascalCase filenames.
- Good:
MatchingDay.tsx - Bad:
Step_0.tsx
- Good:
- Rename
Step_N.tsxstyle files toStepN.tsxduring relevant refactors (Step_1.tsx/Step_2.tsx/Step_3.tsxstill pending;Step0.tsxalready renamed). - Rename typo files when in scope.
- Example:
Carousle.tsx→Carousel.tsx
- Example:
- Prefer named exports.
- Default exports are allowed only for Next.js route files:
page.tsxlayout.tsxerror.tsxloading.tsxnot-found.tsx
All colors, typography, and spacing must use CSS variable tokens.
Color tokens must follow this format:
var(--color-semantic-*)Example mapping:
Semantic/Text/Normal/Strong
→ var(--color-semantic-text-normal-strong)Typography tokens must follow this format:
var(--typography-*-font-size)
var(--typography-*-font-weight)
var(--typography-*-line-height)
var(--typography-*-letter-spacing)Example mapping:
Typography/Body/Medium
→ var(--typography-body-medium-font-size)Hardcoded values are forbidden:
- hex
- rgb
- rgba
- hsl
- px font-size
- fixed numeric line-height
- fallback values like
var(--color-x, #ffffff)
If a hardcoded value cannot be mapped to an existing token, do not choose an approximate token.
Stop and ask the user.
styled-componentsis the only standard styling method.- Do not introduce CSS Modules.
- Do not introduce emotion.
- Do not add new inline styles.
- Existing inline styles should be gradually extracted into styled blocks.
classNameusage should be minimal and limited to library integration insideshared/ui.- Use styled props for conditional styling.
Example:
const Box = styled.div<{ $active?: boolean }>`
color: ${({ $active }) =>
$active
? 'var(--color-semantic-text-normal-strong)'
: 'var(--color-semantic-text-normal-default)'};
`;One-off dynamic layout values may use inline style only when unavoidable.
Allowed example:
<div style={{ width: computedWidth }} />Forbidden examples:
<div style={{ color: '#ffffff' }} />
<div style={{ fontSize: '14px' }} />src/app/admin/** is internal operator tooling, not a user-facing product surface. It is exempt from the strict styling rules in §5.1–§5.2:
- Inline styles and ad-hoc simplified styling are allowed.
- Hardcoded colors/spacing are tolerated (no design-token requirement).
- Raw
alert()/confirm()are allowed in place ofuseToast()/ modal components.
Do not spend cleanup effort converting admin pages to styled-components or tokens unless explicitly asked. All token / styled-components / useToast rules in §4–§5 apply to every non-admin surface.
- Do not add new
any. - Do not add new
@ts-ignore. - Do not add new
@ts-expect-error. - Use
unknownin catch blocks.
Good:
catch (err: unknown) {
if (err instanceof Error) {
console.error(err.message);
}
}Bad:
catch (err: any) {
console.error(err.message);
}- If an external SDK has no type, add a minimal
declare moduleor global declaration. - Keep feature-specific types in
features/<domain>/model/types.ts. - Remove manual types that duplicate generated DTOs.
- Prefer
@/alias imports. - Do not use parent relative imports like
../or../../. - Sibling relative imports like
./Siblingare allowed. - Use
import typefor type imports. - Follow
@typescript-eslint/consistent-type-imports. - Barrel
index.tsfiles are allowed only in limited canonical locations:src/shared/uisrc/components/commonsrc/components/display
- Do not add arbitrary barrel files.
- Do not use default exports except for Next.js route files.
- Do not introduce new global state libraries.
- Do not add zustand, jotai, redux, or similar state tools.
- React Query is outside the current scope.
- Do not introduce React Query.
- Async data should use existing Context or local
useState+useEffect. - Avoid loading the same data independently in multiple places.
- Extract repeated loading logic into hooks.
- Major state restructuring is allowed only in the relevant cleanup batch.
- Mock data lives in MSW handlers under
src/mocks/(handlers.ts+fixtures/), gated byNEXT_PUBLIC_API_MOCKING=enabledviaMswProvider. The oldsrc/lib/mock/chatMockData.tshas already been removed. - Component-level mock branches should be removed only after BE integration is confirmed.
- If BE integration is incomplete, isolate mock code with:
process.env.NODE_ENV === 'development'- Add a short comment explaining why the mock branch remains.
- Dead code removal should be based on
npx knipornpx ts-prune. - Whitelist:
src/shared/lib/api/generated/**src/app/**route files.stories.tsx
- TODO and FIXME comments should be either resolved or converted into issues.
- Do not leave vague TODO comments.
Components over 500 lines are refactor candidates.
Known examples:
src/components/home/GroupMatchingResultModal.tsx(~620)src/app/admin/matches/page.tsx(~590, admin — exempt from styling rules but still a split candidate)src/app/chat/group/[roomId]/_components/_parts/VoteResultsPage.parts.tsx(~550)src/app/chat/group/[roomId]/_components/_parts/GroupVoteCreateModal.parts.tsx(~540)src/app/chat/one-on-one/[roomId]/_components/MessageList.tsx(~480)
(The previously-listed GroupVoteCreateModal.tsx / VoteResultsPage.tsx / VoteSubmissionPage.tsx and home/MatchingDay.tsx have already been split into _parts/.)
When splitting large components:
- Separate pure move commits from refactor commits.
- Avoid logic changes.
- Put local parts in
_parts/. - Put hooks in
features/<domain>/hooks/or local_hooks/. - If the split boundary is ambiguous, stop and ask the user.
After changes, run:
npm run lint && npm run build && npx tsc --noEmitAll three must pass before reporting completion.
If validation fails:
- Identify whether the failure is caused by the current changes.
- Fix failures caused by the current changes.
- Report pre-existing failures clearly.
- Do not claim success if validation failed.
When relevant, manually test:
/home/chat/one-on-one/[roomId]/chat/group/[roomId]- Group vote modal
/onboarding/admin/matches/admin/users/auth/callback
Whenever a new screen/route is developed (or an existing screen's user-facing flow changes meaningfully), you must add or update the corresponding Cypress E2E test. This is mandatory, not optional.
- Test location:
cypress/e2e/<domain>/(see existingmatching-profile/,days/,flows/,smoke/). - Cover the primary happy-path flow of the new screen end-to-end (entry → key interactions → expected result).
- Reuse shared fixtures in
cypress/fixtures/and existing custom commands instead of duplicating setup. - Run the suite before reporting completion:
npm run test:e2e:cypress- A new-screen PR is not complete until its Cypress test exists and passes.
- If the new screen's flow cannot be expressed as a stable E2E test (e.g. external dependency, unfinished BE), stop and ask the user rather than skipping the test silently.
Staging deployment is triggered by pushing commits to the feat/s3-migration branch. Production deployment is triggered manually with workflow_dispatch, normally from main after staging validation.
- The staging workflow is
.github/workflows/deploy-staging.yml: onfeat/s3-migrationpush it runsnpm ci→npm run build(static export to./out) →aws s3 sync ./out s3://<bucket>/staging --delete→ CloudFront/*invalidation. - The production workflow is
.github/workflows/deploy-prod.yml: on manual dispatch it runs the same build and syncs tos3://<bucket>/prod --delete→ CloudFront/*invalidation. test.ditto.picsis routed to the/stagingS3 prefix.ditto.picsandwww.ditto.picsare routed to the/prodS3 prefix.- Uncommitted or unpushed changes are NEVER deployed. Working-tree edits and local
npm run buildoutput (./out) have no effect on the live site until they are committed AND pushed. If "deployment isn't happening," first checkgit statusandgit log origin/feat/s3-migration..feat/s3-migrationfor unpushed work — that is the most common cause.
A staging push/deploy instruction means: commit ALL relevant changes, push to feat/s3-migration, and watch the staging run until it succeeds. A production deploy instruction means: merge or fast-forward the validated changes to main, manually dispatch deploy-prod.yml, and watch that run until it succeeds. Do not stop at "validation passed" — the user expects the code to actually reach the remote and deploy. Run npm run lint && npm run build && npx tsc --noEmit first, then commit and push.
| Resource | Value |
|---|---|
| GitHub repo | ditto-develop/ditto-fe (branch feat/s3-migration) |
| S3 bucket | ditto-pics-247842832483-ap-northeast-2 (region ap-northeast-2) |
| CloudFront distribution | E2IAN5BWR5D33B |
| Domains | ditto.pics, www.ditto.pics, test.ditto.pics (d28wm0h79feewt.cloudfront.net) |
S3 prefixes:
staging/:test.ditto.picsprod/:ditto.pics,www.ditto.pics
To compare deployed vs local content directly: aws s3 ls s3://ditto-pics-247842832483-ap-northeast-2/ --recursive, or aws s3 cp <key> - to inspect a file. Note that _next/static/chunks/* filenames are content-hashed and the build ID differs on every build, so chunk-name diffs are expected noise — compare route/HTML structure and normalized content, not raw filenames.
After pushing, verify GitHub Actions:
gh run watch <run_id> --repo ditto-develop/ditto-feIf the workflow fails:
- Inspect logs immediately.
- Fix the issue.
- Push again.
- Watch the new run.
Do not report deployment success before the workflow succeeds.
Cleanup work must follow:
~/.claude/plans/ditto-fe-migration-fancy-rose.mdRules:
- Branch name:
chore/cleanup-<letter>-<slug>- Example:
chore/cleanup-a-token-replace
- Example:
- One cleanup batch = one PR
- Batch I and J may be split by file or service.
- PR body must include:
- Change scope
- Validation checklist
- Screenshots when visual cleanup is involved
- Mapping/replacement table for batch A
- Spec diff for batch I
- Do not cross batch boundaries.
- Do not mix multiple batches in one PR.
Stop immediately and ask the user if:
- A hardcoded color or font cannot be mapped to an existing token.
- Generated API and legacy API specs differ.
- It is unclear whether BE integration is complete before removing mock code.
- The split boundary of a large component is ambiguous.
- A public API name or signature must change.
- A required logic change exceeds the current cleanup batch.
- The Claude plan conflicts with this document.
- The task requires modifying out-of-scope projects.
Do not guess. Do not choose approximate values. Do not silently change scope.
When asked to plan a task, use this format:
## Task Understanding
Briefly explain what needs to change.
## Relevant Files
- `path/to/file`: why it matters
## Current Flow
Explain how the existing code works.
## Proposed Implementation
Step-by-step implementation plan.
## Risks
- Risk 1
- Risk 2
## Validation Plan
- Command or manual check
- Command or manual check
## Codex Prompt
A concise prompt that can be pasted into Codex for implementation.Do not edit files during planning.
When reviewing Codex output, use this format:
## Review Summary
Overall assessment.
## Blocking Issues
Issues that must be fixed before merge.
## Non-blocking Suggestions
Nice-to-have improvements.
## Validation Review
- Commands run
- Missing validation
- Smoke tests needed
## PR Risk
Low / Medium / High
## Suggested Codex Fix Prompt
A concise prompt that can be pasted into Codex to fix the issues.If there are no blocking issues, clearly say:
No blocking issues found.When preparing a PR description, use this format:
## Summary
- Change 1
- Change 2
- Change 3
## Why
Explain the motivation.
## Changes
- `file/path`: what changed
- `file/path`: what changed
## Validation
- [ ] `npm run lint`
- [ ] `npm run build`
- [ ] `npx tsc --noEmit`
- [ ] Smoke test completed
- [ ] Deployment workflow passed
## Risks
- Risk or "None known"
## Screenshots / Evidence
Add if applicable.- Be direct.
- Separate facts from assumptions.
- Do not over-explain obvious code.
- Prefer concrete file-level feedback.
- Provide Codex-ready prompts when follow-up implementation is needed.
- Do not claim validation passed unless there is evidence.