매칭되면 버튼 선택(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>
12 KiB
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.
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:
- 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. - Placement surface (
hooks/surface,components/floor-plan/modal/placementShape…): slope, placement/shape drawing, object placement, bulk delete. - 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.mjssetsresolve.fallback = { fs: false }and externalizescanvas,utf-8-validate,bufferutilas commonjs. Adding a node-only dep usually means adding it to that externals list.experimental.staleTimesare 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 serveneeds./certificates/key.pemandcert.pem(gitignored).
Conventions that are easy to violate
-
Prettier: single quote, no semicolons, tabWidth 2, printWidth 150, trailingComma all (
.prettierrc). Runyarn lintbefore declaring done. -
No raw
alert()— always go throughuseSwal/useMessage+sweetalert2. Seedocs/메세지 처리 가이드.pdf. -
Path alias — prefer
@/...over relative../../..chains. -
Logging — never
console.logdirectly in shipped code. Usesrc/util/logger.js, which no-ops unlessNEXT_PUBLIC_ENABLE_LOGGING=true.src/util/debugCapture.jsPOSTs to/api/debugfor 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.*.localis 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-기반
callsedge 는 INFERRED 로 마크됨 — 호출 의미가 답의 핵심이면 소스로 한 번 더 확인.