[작업내용] : - PDF-import 절에 roofShape descriptor 반환·normalizeRoofShape·pdfRoofShapeDescriptorState 저장·useRoofShapeSetting pre-select(consume-once) 흐름 기술 - handleSave 비자동(사용자 [적용]) 명시 + Gemini roofShape 런타임 QA 잔여 경고 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
12 KiB
Markdown
122 lines
12 KiB
Markdown
# 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` 동기화로만 처리한다. **지붕형상 자동 선택(pre-select)** — 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` 으로 pre-select 만** 한다(`handleSave` 자동호출 금지 — 적용은 사용자 [적용]) + consume-once 로 atom 리셋(비-PDF 재진입 stale 방지). 매처 null(복합형)·roofShape 누락은 강제선택 없이 graceful. ⚠️ 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 로 마크됨 — 호출 의미가 답의 핵심이면 소스로 한 번 더 확인.
|