qcast-front/CLAUDE.md
sangwook yoo 83783c869b feat(roof-shape): PDF 지붕형상 매칭 시 설정완료 자동 적용
매칭되면 버튼 선택(setShapeNum) 후, shapeNum 이 matched 로 커밋된 뒤
[shapeNum] effect 에서 handleSave(설정완료)를 자동 호출해 지붕형상까지 적용한다.
마운트 effect 에서 즉시 호출하면 stale shapeNum(기본값 1)으로 잘못된 패턴이
적용되므로 autoSaveShapeNumRef 가드 + 별도 effect 경유로 처리.
매칭 결과는 콘솔에 ' N번 버튼 매칭 → 자동 선택 + 설정완료' / ' 매칭 없음'
한 줄로 명시. CLAUDE.md PDF-import 절을 'pre-select만 → 설정완료 자동 적용'으로 갱신.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:53:11 +09:00

122 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
> 루트 `AGENTS.md` 는 `@CLAUDE.md` 한 줄짜리 alias. Codex 등 다른 코딩 에이전트도 이 문서를 그대로 따른다.
> `README.md` 는 사실상 `create-next-app` 보일러플레이트 그대로다 — 신뢰하지 말 것. 스택·디렉토리·env·라우팅·트러블슈팅의 단일 진실 공급원은 이 파일과 `docs/` PDF 들이다.
## Project at a glance
Q.CAST Front — a Next.js 14 (App Router) frontend for solar-panel layout planning, quoting, and management, targeted at the Japanese (Q.CELLS) market with `ja`/`ko` i18n. JavaScript + TypeScript (`allowJs`, `strict: false`). Path alias `@/* → ./src/*`.
## Common commands
All scripts use `env-cmd` to inject the right `.env.<mode>` file. There is **no test runner** wired into `package.json`.
```bash
yarn dev # local dev (.env.localhost)
yarn dev:local-api # local dev pinned to http://localhost:8080 API
yarn local:dev # .env.local.dev mode
yarn build # production build (.env.production)
yarn build:dev # development build (.env.development)
yarn start:cluster1 # next start -p 5000 (.env.production) — used by PM2
yarn start:dev # next start -p 5010 (.env.development)
yarn serve # custom HTTP:3000 + HTTPS:3001 (needs ./certificates/*.pem)
yarn lint # next lint
```
PM2 entrypoints (`ecosystem.config.js`, `prd1/prd2/dev/dev.local.ecosystem.config.js`) wrap `startscript*.js`, which `child_process.exec` the yarn scripts above.
## Architecture — the parts that span many files
### Mode routing through `src/config/`
Runtime config is selected at module load by `src/config/config.export.js`, which switches on `process.env.NEXT_PUBLIC_RUN_MODE` between `config.local.js`, `config.local.dev.js`, `config.development.js`, `config.production.js`. Never read env values directly in feature code — import from here so mode switches stay in one place.
### Session / auth gate
`iron-session` cookies are the session source of truth (`src/lib/session.js`, `src/lib/authActions.js`). `src/app/layout.js` checks the session at the root and redirects every non-login route to `/login` when missing. `SESSION_SECRET` must be set (≥32 bytes). The session also carries `storeLvl` (1차/2차 판매점) which drives quote-render branches, builder number, and group ID. Q-Order / Q-Musubi support **autoLogin** flows via `NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL` / `NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL`.
`src/middleware.js` injects `x-pathname` into request headers so the root layout can branch on the active route.
### Floor Plan canvas (the heart of the app)
`src/app/floor-plan/**` is built on **Fabric.js 5.5.2**. The model has three layered concerns:
1. **Roof covering** (`hooks/roofcover`, `components/floor-plan/modal/roofShape` / `outerlinesetting` / `eavesGable` …): outer wall lines, auto/manual roof-shape detection, guides, eaves & kerabs, flow up/down, outer-line edit & offset, roof-face assignment.
2. **Placement surface** (`hooks/surface`, `components/floor-plan/modal/placementShape` …): slope, placement/shape drawing, object placement, bulk delete.
3. **Module / circuit** (`hooks/module`, `components/floor-plan/modal/panelBatch` …): basic settings, circuit & rack config, orientation.
A modal under `components/floor-plan/modal/<feature>` pairs with a hook under `hooks/<roofcover|surface|module|object|option|...>`. Keep that pairing when adding new tools.
**Fabric extensions** live in `src/components/fabric/QLine.js`, `QPolygon.js` and `src/util/fabric-extensions.js`. Prototype patches must be defined in one place and loaded together — patching elsewhere causes hard-to-debug shape issues.
**Context menus** are unified through `useContextMenu` + the `contextMenu` Recoil atom. The per-target menu inventory is documented in `docs/Canvas Status.md` — match that list when changing menu wiring.
**Plan persistence** (serialize/restore) is in `src/lib/canvas.js` and `src/util/canvas-util.js`. Recent commits (`a5e794b3`, `2aef32e0`) added a three-layer guard (entry / cache / save) and `SizeSetting` input guards against 0-area apertures/shadows/dormers — when touching plan load/save or size inputs, do not weaken or remove these guards without understanding the failure mode (`InvalidStateError` from corrupted plans).
**PDF 도면 임포트** — 배치면 초기설정 모달의 PDF 입력 모드 → `src/app/api/gemini/floor-plan/route.js`(Gemini 분석, 세션 인증 + 사용자별 분당 5회 레이트리밋) → `src/hooks/pdf-import/usePdfImport.js`. **분석 직후 외벽선은 항상 미확정으로만 그린다**(`PlacementShapeSetting.handleSaveBtn``applyOuterline(mmPoints, { autoFix:false })``useLine.addLine` 경유 outerLine 생성(attributes/planeSize/fontSize) + `outerLinePoint` 정점 circle, `outerLineFix=false`). 그 후 `handleSaveBtn``setSelectedMenu('outline')` + `setCurrentMenu(MENU.ROOF_COVERING.AUTO_OUTERLINE_FIX)` 단일 전환만 한다 — `MenuDepth01``currentMenu` 변화를 감지해 `useMenu.handleMenu``AUTO_OUTERLINE_FIX` case 로 **'외벽선 자동 확정' 팝업(`AutoOuterLineFixSetting`)** 을 자동으로 띄운다. **확정 여부 분기는 이 팝업의 스위치(`autoOuterLineFixState`, 기본 ON) + [확인]** 에서 한다(`handleConfirm`): **ON** — 외벽선/정점/points 는 이미 `applyOuterline` 이 그려둔 상태라 `canvas.outerLineFix=true` + `setOuterLineFix(true)` 플래그만 켜면 수동 확정(`useOuterLineWall.handleFix`) 직후와 동일해진다(WALL/ROOF 폴리곤은 지붕형상 설정 적용 시점에 생성). 미확정으로 저장된 직전 상태를 덮어쓰도록 `saveCanvas` 한 뒤 `setCurrentMenu(ROOF_SHAPE_SETTINGS)`. **OFF** — 미확정 유지 + `setCurrentMenu(EXTERIOR_WALL_LINE)` 로 외벽선 확정 팝업(`WallLineSetting`)으로 보낸다. **팝업 전환은 `currentMenu` 메커니즘으로만** — 직접 `addPopup` 하지 말 것(MenuDepth01 의 `closeAll` 과 충돌). 신규 메뉴 `AUTO_OUTERLINE_FIX``subMenusState` 에 노출하지 않는 일회성 팝업이다. ⚠️ `basicSettingSave({ skipSideEffects:true })` 는 메뉴를 건드리지 않아야 한다 — `setMenuByRoofSize``if (!skipSideEffects)` 밖에 있으면 async 저장(`await post`) 직후 `currentMenu``EXTERIOR_WALL_LINE` 로 덮어써, AUTO_OUTERLINE_FIX 팝업 대신 WallLineSetting 으로 되돌아간다(2026-06-21 버그). **좌표에 축소 scale 을 곱하지 말 것** — 'QLine 길이×10 = 표시 mm' 불변식이 깨진다. 화면맞춤은 `viewportTransform` 줌 + `canvasZoomState` 동기화로만 처리한다. **지붕형상 자동 선택 + 설정완료 자동 적용** — Gemini 응답은 `roofShape`(RoofShapeDescriptor `{type,ridge,flows,confidence?}`, route.js 가 `normalizeRoofShape` 로 어휘 정규화)를 추가로 반환한다. `analyzePdfAndApply` 가 이를 `pdfRoofShapeDescriptorState`(outerLineAtom)에 저장(분석 시작 시 null 리셋)하고, ROOF_SHAPE_SETTINGS 진입 시 `useRoofShapeSetting` 마운트 effect 가 `matchRoofShapePattern`(`@/common/roofShapePattern`, flows 집합+type/ridge guard)으로 매칭해 **버튼을 `setShapeNum` 으로 선택**하고, `shapeNum` 이 matched 로 커밋된 뒤 별도 effect(`autoSaveShapeNumRef` 가드, deps `[shapeNum]`)가 **`handleSave`(설정완료)를 자동 클릭**해 지붕형상까지 적용한다(2026-06-25 사용자 지시로 'pre-select만 → 설정완료 자동 적용'으로 전환). ⚠️ **마운트 effect 에서 `setShapeNum(matched)` 직후 같은 틱에 `handleSave` 를 호출하지 말 것**`handleSave``shapeNum` 을 클로저로 읽어 stale(기본값 1) 로 잘못된 패턴이 적용된다. 반드시 `[shapeNum]` effect 로 커밋 후 실행. consume-once 로 atom 리셋(비-PDF 재진입 stale 방지). 매처 null(복합형)·roofShape 누락은 자동선택/적용 없이 graceful(수동 선택). 매칭 결과는 콘솔에 `✅ N번 버튼 「…」 매칭 → 자동 선택 + 설정완료` / `❌ 매칭되는 버튼 없음`(NEXT_PUBLIC_ENABLE_LOGGING=true)으로 명시. ⚠️ Gemini 의 실제 `roofShape` 출력 품질(특히 片流れ flows 방향)은 런타임 수동 QA·프롬프트 튜닝 영역.
### State — Recoil, split by domain
Atoms live under `src/store/` and are split by domain (`canvasAtom`, `roofAtom`, `estimateAtom`, `modalAtom`, `menuAtom`, `settingAtom`, …). When adding state, find the existing domain atom rather than introducing a new global. `RecoilWrapper.js` mounts the root; `QcastProvider.js` composes the rest of the global providers (session, global data, global loading) for the App Router root layout.
### Build / runtime quirks
- `next.config.mjs` sets `resolve.fallback = { fs: false }` and externalizes `canvas`, `utf-8-validate`, `bufferutil` as commonjs. Adding a node-only dep usually means adding it to that externals list.
- `experimental.staleTimes` are all **0** on purpose — App Router cache is disabled so plan/estimate pages always refetch. Don't re-enable without checking the plan-stale failure mode.
- `reactStrictMode: false`. Don't assume double-render semantics.
- HTTPS local dev via `yarn serve` needs `./certificates/key.pem` and `cert.pem` (gitignored).
## Conventions that are easy to violate
- **Prettier**: single quote, **no semicolons**, tabWidth 2, printWidth 150, trailingComma all (`.prettierrc`). Run `yarn lint` before declaring done.
- **No raw `alert()`** — always go through `useSwal` / `useMessage` + `sweetalert2`. See `docs/메세지 처리 가이드.pdf`.
- **Path alias** — prefer `@/...` over relative `../../..` chains.
- **Logging** — never `console.log` directly in shipped code. Use `src/util/logger.js`, which no-ops unless `NEXT_PUBLIC_ENABLE_LOGGING=true`. `src/util/debugCapture.js` POSTs to `/api/debug` for server-side capture (also gated by the same flag).
- **Commit messages** follow `.gitmessage.txt`:
```
[일감번호] : [제목]
[작업내용] :
```
Recent commits use a slightly tighter `[2204] <subject><detail>` form; mirror neighboring commits on the branch.
- **`.env.*`** can contain real secrets — never paste contents into PR bodies, issues, or chat. `.env.*.local` is gitignored for machine-local overrides.
## Reference docs in `docs/`
- `Canvas Status.md` — canvas feature status and right-click menu inventory per target type.
- `Qcast coding convention.pdf`, `Qcast development guilde.pdf` — full conventions / dev guide.
- `메세지 처리 가이드.pdf` — modal & alert message rules.
- `git commit message Convention.pdf` — commit format.
- `Nextjs 14 컴포넌트에 대해서….pdf` — App Router server/client component notes.
- `dictionary.txt` — domain vocabulary (roof / panel terms in JP/KR).
## Always Do
- All answers and reasoning processes must be written in Korean.
- Once the task is complete, use a sub-agent to perform lint checks, and build checks.
- When performing a lint check, ensure that any errors are resolved before proceeding, and make an effort to resolve any warnings that arise.
- When committing, write the prefix in English, while the rest of the title and content in Korean.
- Upon task completion, update the CLAUDE.md and README.md documents if necessary.
- When working, commit in meaningful units that can be explained in a single sentence.
## `.agents/` skill 문서
`.agents/skills/{next-best-practices,vercel-react-best-practices}/**` 에 Vercel·Next.js 모범사례 룰북이 들어 있음(총 95개 .md). Next/React 패턴 관련 결정을 내릴 때(렌더링 최적화, RSC 경계, 번들 분할, hydration 이슈, 서버/클라이언트 캐싱 등) 우선 참조.
- Skill 진입점: 각 디렉토리의 `SKILL.md` 가 인덱스, 룰은 `rules/` 하위 토픽별 파일.
- 이 문서들은 git 추적 대상이므로 변경하면 PR 에 함께 올릴 것.
## graphify (선택)
`src/` 지식 그래프를 `graphify-out/` 에 캐싱해두면 cross-module 질문(A 가 B 에 어떻게 연결되는가, X 의 의존성 등)을 grep 보다 빠르게 답할 수 있다. 캐시는 로컬 전용이며 git 추적 대상이 아니다.
- 부재 시(현재 워크트리 포함): `/graphify src` 로 풀 빌드, 이후엔 `graphify update .` 로 incremental.
- `src/` 편집 후 architectural 질문에 답하기 전에 `graphify update .` 1회 실행 (AST-only, 수 초).
- JS import-기반 `calls` edge 는 INFERRED 로 마크됨 — 호출 의미가 답의 핵심이면 소스로 한 번 더 확인.