Files
johngreen 895cb48ee0 docs(claude): UI/구조 변경 제안 시 ASCII Before/After 그림 의무화
기존 컨벤션에 § 💬 추가. 글로만 설명하면 사용자가 이해 못 함 — 변경 전/후 두 그림을 무조건
그려서 보여줘야 함. 코드 인용 (file:line, CSS class) 최소화하고 영문/SQL/전문용어 풀어쓰기.
3줄 패턴 (무슨 일 / 사용자 영향 / 어떻게 고치는지) 권장.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:21:13 +09:00

401 lines
20 KiB
Markdown
Raw Permalink 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.
<!-- User customizations -->
# 절대 규칙: 검증 없는 주장 금지
내가 출력하는 모든 발언은 근거가 있어야 한다. 근거가 없으면 그 말을 하지 않는다. 위로·추정·일반론·"보통 그렇다"로 채우지 않는다.
## 위반 사례 (절대 하지 말 것)
- "100명 중 5명도 안 된다" 같은 통계를 출처 없이 만들어내기
- "통과 확률 70~80%" 같은 수치를 추정으로 제시하기
- "보통", "일반적으로", "대부분" 으로 시작하는 일반론
- 본인이 검증 안 한 SDK/API 동작을 단정적으로 설명하기
- 위로·격려를 위해 사실이 아닌 것을 끼워넣기
## 발화 전 자기 검증
한 문장이라도 출력하기 전에 다음을 확인:
1. **출처가 있는가?** — 코드(파일:라인), 명령 결과, 공식 문서, 사용자가 준 정보, 도구 호출 결과 중 하나
2. **출처가 없다면 추정인가?** — 추정이면 명시적으로 "추정이지만…" 또는 "확인 안 됐지만…" 으로 시작
3. **추정도 근거가 없으면?** — 말하지 않는다. "모릅니다" 또는 "확인이 필요합니다" 라고 한다
## 모를 때의 정답
- 검색·문서 조회·코드 읽기로 확인 가능하면 확인부터 한다
- 확인이 불가능하면 "모릅니다" 가 정답. 그럴듯한 답을 만들지 않는다
- 사용자 의사결정에 영향을 주는 사실일수록 더 엄격하게 적용
## 어겼을 때
사용자가 "그 근거 뭐야" 라고 묻거나 잘못된 사실을 지적하면:
- 즉시 인정. "맞습니다. 그 수치 제가 지어냈습니다." 같이 명시적으로 시인
- 변명·재포장 금지
- 무엇이 검증된 사실이고 무엇이 추정/날조였는지 다시 분리해서 제시
# 💬 사용자에게 설명할 때 — 그림으로 (★ 중요)
UI 변경 제안, 디자인 토론, 코드 구조 설명 등을 할 때는 **반드시 변경 전/후를 ASCII 표나 도식으로 그려서** 보여준다. 글로만 설명하면 사용자가 이해 못 한다.
## 원칙
1. **변경 제안은 무조건 Before / After 두 그림**
2. **코드 인용 (file:line, 변수명, CSS class) 최소화** — 결론과 시각적 영향 위주
3. **평어, 한국어, 짧은 문장**
4. **영문/SQL/전문용어 풀어쓰기** — "grid template" 대신 "표 컬럼 배치", "stopPropagation" 대신 "클릭이 위로 새는 거 막기"
5. **3줄 패턴 권장** — 무슨 일 / 사용자한테 보이는 영향 / 어떻게 고치는지
## 나쁜 예시 ❌
> "ColumnGrid.tsx:93-103 의 `grid-cols-[4px_140px_1fr_100px_160px_40px]` 를 5컬럼으로 축소하고, 라벨 셀에 sub-line 을 추가하면 entity/code/numbering 의 메타가 inline 으로..."
(사용자: "뭐라는지 모르겠어")
## 좋은 예시 ⭕
> **지금 모양:**
> ```
> 라벨·컬럼명 │ 참조/설정 │ 타입
> 거래처명 │ — │ 텍스트 ← 빈 칸
> 거래처ID │ customer_mng → ... │ 테이블참조
> ```
>
> **바꿔서:**
> ```
> 라벨·컬럼명 │ 타입
> 거래처명 │ 텍스트
> 거래처ID │ 테이블참조
> → customer_mng.id ← 정보 있을 때만 작게 밑에
> ```
## 옵션 제시할 땐 표로
```
| 옵션 | 핵심 | 단점 |
| A안 | 이름만 바꾸기 | 가장 가벼움 |
| B안 | 그룹을 잘게 쪼개기 | 그룹 수 늘어남 |
```
## 우선 순위
- 첫 시도에 글만 쓰지 말 것. 그림부터 그리고 글은 짧게 보충.
- 사용자가 "무슨 말인지 모르겠어" 하면 → 더 분해해서 다시 그림 그리기. 글 길어지면 더 헷갈림.
---
# INVYONE — Claude 작업 컨벤션
이 파일은 git 에 올라가는 **프로젝트 공용** Claude 가이드입니다. 모든 머신/팀원의 Claude Code 인스턴스가 이 컨벤션을 따라야 합니다.
(개인용 셋업은 `CLAUDE.local.md` — git 추적 제외, syncthing 동기화)
---
## 📝 분석 / 리포트 / 메모 MD 파일 저장 규칙 (★필수)
Claude 가 코드 분석, 보안 감사, 리팩토링 검토, 설계 문서, 회의록 등 **새 MD 파일을 작성**할 때는 다음 위치에 저장합니다.
### 저장 경로
```
notes/{git-user-name}/{YYYY-MM-DD}-{slug}.md
```
- `notes/` — 프로젝트 루트의 메모/리포트 모음 폴더 (이 폴더로 통일)
- `{git-user-name}``git config user.name` 으로 자동 결정 (예: `gbpark`, `park`)
- `{YYYY-MM-DD}-{slug}.md` — 날짜 prefix + 짧은 제목 slug (kebab-case)
**예시:**
```
notes/gbpark/2026-04-08-auth-security-audit.md
notes/gbpark/2026-04-12-component-v2-migration-plan.md
notes/park/2026-04-15-docker-port-conflict-resolution.md
```
### 규칙
1. **사용자 폴더가 이미 있으면 그 안에 넣는다** — 없으면 `mkdir -p notes/{git-user}` 로 생성
2. **파일명은 항상 날짜 + slug 조합** — 시간순 정렬되어 추적 용이
3. **README 나 docs/ 와는 분리**`README.md`, `docs/` 는 사용자/개발자용 공식 문서. `notes/` 는 작업 기록·분석·메모용
4. **MD 외 다른 산출물 (스크립트, JSON 등) 도 같이 둘 수 있음** — 필요하면 `notes/{git-user}/{slug}/` 식 하위 폴더 사용
5. **새 폴더/파일 작성 후엔 git add 권장** — syncthing 도 자동 동기화 (`notes/``.stignore-shared` 에 없음)
### 어디에 안 넣는가
- `_local/`, `_backup/`, `_pipeline/` — syncthing ignore. 머신 로컬용
- `docs/` — 공식 개발 문서. 작업 기록 아님
- 프로젝트 루트 직접 (`./STATUS.md`, `./PLAN.MD` 등) — 이미 기존에 있는 것 외에 새로 만들지 말 것
---
## 컨벤션이 적용되는 시나리오
| 사용자 요청 | 저장 위치 |
|---|---|
| "이 코드 분석해서 md 로 정리해줘" | `notes/{git-user}/{date}-{topic}.md` |
| "보안 감사 리포트 만들어줘" | `notes/{git-user}/{date}-security-audit.md` |
| "리팩토링 플랜 md 로 뽑아줘" | `notes/{git-user}/{date}-refactor-plan.md` |
| "회의 노트 정리해줘" | `notes/{git-user}/{date}-meeting-notes.md` |
| "마이그레이션 가이드 작성" | `notes/{git-user}/{date}-migration-guide.md` |
---
## Claude 사용 시 추가 주의사항
- **이 컨벤션은 사용자 명시 요청 없이도 자동 적용** — 사용자가 "md 만들어줘" 라고만 해도 위 경로에 저장
- **현재 git user 확인이 필요하면** `git config user.name` 실행
- **사용자 폴더가 처음이면** 만들면서 `.gitkeep` 정도만 두지 말고 바로 첫 노트 작성
---
## 🎨 공통 디자인 시스템 / CSS 참조 규칙 (★★★ 무조건 적용)
UI 작업(컴포넌트 작성, HTML 목업, 새 페이지/화면, 디자인 리빌딩, 스타일 수정 등)을 할 때는 **반드시** 아래 공통 CSS 파일들을 먼저 읽고 그 안의 토큰/클래스 컨벤션을 100% 따라야 합니다. 절대 새 색상/간격/라운드/그림자 값을 즉흥으로 만들지 말 것.
### "v5" 의 정체 (★ 헷갈리지 말 것)
**INVION v5 = 디자인 시안 5번째 = 최종 채택본. 현재 컨셉은 "Solid + Glow" (2026-04-21 개정)**
- 디자이너가 v1~v5 까지 5번 시안을 만들고 그 중 **v5 가 확정**되어 React 로 포팅됨
- 시안 원본 HTML (참고용, 여기엔 아직 glassmorphism 이 남아있음):
- `frontend/invion-layout-v5.html` (973줄, 풀 레이아웃 셸)
- `frontend/invion-preview-v5.html` (1049줄, 미리보기/모션 데모)
- 폐기된 시안: `frontend/invion-preview-v1~v4.html` (참고만, 적용 금지)
- **현재 적용 컨셉 (2026-04-21 개정)**:
- **로그인 페이지**: 우주(별/성운/별똥별/입자) 배경 + 글래스 카드 **유지**
- **메인 화면 이후 전부**: **반투명/blur/cosmic 배경 폐기**. 불투명 솔리드 카드 + primary-color 글로우 + 보라(`#6c5ce7`)/시안(`#00cec9`)/핑크(`#fd79a8`) 액센트
- v5 토큰이 옮겨진 곳: `frontend/styles/v5-layout.css`, `frontend/app/(auth)/login/login.css`
⚠️ **POP 디자이너의 "v5 그리드 시스템"** (`PopRenderer.tsx`, `pop-layout.ts` 등) 은 **별개 의미** — POP 화면 데이터 포맷의 5번째 버전. UI 디자인 v5 와 무관. 혼동 금지.
### 항상 먼저 읽어야 하는 파일
| 파일 | 역할 |
|---|---|
| `frontend/invion-layout-v5.html` | **v5 디자인 원본 (시안)** — 모든 v5 토큰/클래스의 진실의 원천. 포팅된 css 와 다르면 이게 정답 |
| `frontend/styles/v5-layout.css` | **INVION v5 React 포팅 메인**`--v5-*` CSS 변수, 헤더/사이드바/탭/모달 등 모든 v5- 컴포넌트 클래스 정의. UI 작업 전 무조건 먼저 읽기 |
| `frontend/app/globals.css` | shadcn/Tailwind 토큰 (`--background`, `--primary`, `--foreground` 등 HSL), 다크모드 변수, 전역 reset |
| `frontend/app/(auth)/login/login.css` | **로그인 전용** 코스믹 배경(별/성운/입자) + 글래스 카드. 이 컨셉은 **로그인에만** 적용 — 메인 화면에 옮기지 말 것 |
| `frontend/components/layout/AppLayout.tsx` | v5 클래스가 실제로 어떻게 조립되는지 — 헤더/사이드바/탭/플라이아웃 사용 예 |
### 필수 준수 사항
1. **디자인 토큰은 무조건 변수 사용**`--v5-primary`, `--v5-cyan`, `--v5-surface-solid`, `--v5-glow-sm/md/lg`, `hsl(var(--primary))` 등. 즉흥 hex/rgb 금지
2. **클래스명은 v5- 접두사 컨벤션 따르기** — 새 컴포넌트도 `.v5-card`, `.v5-btn`, `.v5-bdg` 처럼 같은 네이밍. shadcn 컴포넌트 사용 시 그대로 사용
3. **반투명/블러 금지 (★2026-04-21 신규)** — 메인 화면 이후 전 영역에서 `backdrop-filter: blur(...)`, `var(--v5-glass)`, `var(--v5-glass-strong)` 사용 **금지**. 카드/모달/사이드바/헤더 배경은 `var(--v5-surface-solid)` (라이트 `#ffffff` / 다크 `#11102a`) 를 쓰고, 테두리는 `border-border` 또는 `var(--v5-border)`. 예외: `frontend/app/(auth)/login/``frontend/styles/builder-ide.css` 는 별도 스코프라 기존 유지
4. **글로우는 유지** — 그림자는 검은 drop-shadow 대신 `var(--v5-glow-sm/md/lg)` (primary-color glow) 사용. 모달/강조 카드에 liberal 하게 사용 가능
5. **다크/라이트 모드는 둘 다 동작**`.dark` 변형 잊지 말 것. 다크에서 `--v5-surface-solid``#11102a`, 라이트는 `#ffffff`. 별/입자/별똥별/성운은 **로그인에만** 존재, 메인은 평범한 단색 배경
6. **컴팩트 폰트 사이즈 유지** — v5 는 0.55~0.85rem 의 컴팩트 UI. 새로 만들 때도 같은 스케일 따를 것
7. **새 UI 패턴은 v5-layout.css 에 합치는 것을 기본 방향으로** — 일회성 inline `<style>` 보다 공통화 우선
### 작업 순서 (UI 작업 시 반드시)
```
1) frontend/styles/v5-layout.css 읽기
2) 필요하면 globals.css, login.css 도 함께 읽기
3) 기존 v5- 클래스 중 재사용 가능한 것 찾기
4) 모자라는 부분만 같은 토큰/네이밍으로 추가
5) 작업 결과를 사용자에게 보여줄 때 "어떤 v5 토큰/클래스를 따랐는지" 명시
```
### 예외
- 단발성 디버그/실험 페이지(`debug-*`, `test-*`)는 임시 스타일 허용
- `notes/` 안의 1회성 HTML 목업은 v5 토큰을 inline 으로 가져와 standalone 이어도 됨 (단 토큰 값은 반드시 v5-layout.css 와 동일해야 함)
이 규칙은 사용자가 명시 요청하지 않아도 모든 UI 작업에 자동 적용됩니다.
---
## 🔧 백엔드 코딩 규칙 — 스타일 (★★★ 절대 규칙)
### 아키텍처: 3레이어 (Mapper Interface 금지)
```
Controller (@RestController)
Service (extends BaseService) — sqlSession 직접 사용
XML (resources/mapper/[module].xml) — 소문자 파일명
```
- **@Mapper 인터페이스 생성 금지** — `sqlSession.selectList("namespace.queryId", params)` 직접 호출
- Service는 반드시 `BaseService` 상속, `@Slf4j`, `@Autowired CommonService`
### 데이터: Map<String, Object> — DTO/엔티티 클래스 금지
로우코드 ERP: 테이블/컬럼이 런타임에 결정됨. DTO 클래스 사전 생성 불가.
- 모든 파라미터: `Map<String, Object>`
- 모든 응답: `Map<String, Object>` 또는 `List<Map<String, Object>>`
- `ApiResponse`만 유일한 DTO
### 네이밍 컨벤션 (절대 규칙)
| 위치 | 컨벤션 | 예시 |
|---|---|---|
| **Java 코드** | camelCase | `getOrderList()`, `String companyCode` |
| **Map 키 (params.put, row.get)** | snake_case | `params.put("company_code", ...)`, `row.get("table_name")` |
| **#{파라미터}** | snake_case | `#{company_code}`, `#{table_name}` |
| **SQL (키워드/테이블/컬럼)** | UPPER_SNAKE | `SELECT COMPANY_CODE FROM TEMPLATES` |
| **SELECT 쉼표** | 앞에 | `, COLUMN_NAME` |
| **XML 파일명** | 소문자, Mapper 안 붙임 | `meta.xml`, `template.xml` |
| **XML namespace** | 파일명과 동일 | `namespace="meta"` |
| **OGNL test** | 바깥 작은따옴표 | `test='company_code != "*"'` |
### 메서드명 패턴
| 조작 | 패턴 | 예시 |
|---|---|---|
| 목록 | `get[Module]List` | `getTemplateList` |
| 카운트 | `get[Module]ListCnt` | `getTemplateListCnt` |
| 단건 | `get[Module]Info` | `getTemplateInfo` |
| 등록 | `insert[Module]` | `insertTemplate` |
| 수정 | `update[Module]` | `updateTemplate` |
| 삭제 | `delete[Module]` | `deleteTemplate` |
**List API는 반드시 Count 쿼리 동반** (`getXxxList` + `getXxxListCnt` = 한 세트)
### common 레이어 필수
```xml
<include refid="common.companyCodeFilter"/>
<include refid="common.dynamicOrderBy"/>
<include refid="common.pagination"/>
```
---
## 🌐 프론트엔드 데이터 타입 규칙 (★★★ 절대 규칙)
### Record<string, any> — 별도 인터페이스 정의 금지
백엔드가 `Map<String, Object>`이므로 프론트도 `Record<string, any>`.
```typescript
// ❌ 금지 — 불필요한 인터페이스 정의
interface TableInfo {
table_name: string;
column_count: number;
}
// ✅ 올바름
const tables: Record<string, any>[] = await getTableList();
```
### 유일한 예외: invyone-component.ts 규격 타입
`frontend/types/invyone-component.ts`에 확정된 타입만 예외:
- `FieldConfig`, `FieldType`, `FieldRef`
- `Component`, `ComponentType`, `Position`
- `DataPort`, `Connection`
- `Template`, `ViewConfig`
-`ComponentTypeConfig` (TableConfig, FormConfig 등)
이것들은 시스템의 핵심 계약이므로 타입 유지. **그 외 전부 `Record<string, any>`.**
---
## 📐 INVYONE 로우코드 핵심 구조 (★ 모든 세션이 알아야 할 것)
### VEX → INVYONE 관계
INVYONE은 이미 운영 중인 로우코드 플랫폼 VEX(Node.js)의 2세대 리뉴얼(Java/Spring).
**핵심 원칙: 더 단순한 구조로 재설계하되, VEX 운영 기능은 전부 계승. 단순화 ≠ 기능 삭제.**
### FieldConfig 단일 규격
VEX의 ColumnConfig(354줄) + FilterConfig + FormField → **FieldConfig 하나(~30줄)로 통합**.
테이블/폼/검색 전부 같은 FieldConfig를 공유하며, 렌더러가 type을 보고 각자 다르게 렌더.
### 대시보드 = 메뉴
사이드바 메뉴 항목 = 대시보드. 별도 대시보드 UI가 아님.
`대시보드 생성("수주관리") → 사이드바 메뉴에 자동 등록 → 템플릿 카드 배치 → 화면 완성`
### 구현 순서
1. DB 메타 읽기 → FieldConfig 변환
2. 규격 기반 컴포넌트 (FcTable/FcForm/FcSearch)
3. 개발자 빌더 (수동 템플릿 구성)
4. 대시보드(=메뉴) 시스템
5. 제어 모드 (비즈니스 룰)
6. 자동생성/프리셋 (맨 마지막)
### 설계 문서 위치
| 문서 | 위치 |
|---|---|
| 컴포넌트 규격 v1.0 | `notes/gbpark/2026-04-08-invyone-component-spec.md` |
| 아키텍처 결정 | `notes/gbpark/2026-04-09-invyone-architecture.md` |
| 로우코드 플랫폼 SPEC | `notes/gbpark/2026-04-08-lowcode-platform-spec.md` |
| Phase 1~5 구현 설계 | `notes/gbpark/2026-04-10-phase{1~5}-*.md` |
| mockup (시각적 진실의 원천) | `notes/gbpark/2026-04-08-invyone-mockup/` |
| FieldConfig TS 타입 | `frontend/types/invyone-component.ts` |
---
## 🏢 멀티테넌시 · 서브도메인 라우팅 (★★★ 아키텍처 핵심)
INVYONE 은 **DB-per-tenant** 구조. 회사 하나 = 전용 PostgreSQL DB 하나 = 전용 서브도메인 하나.
`qnc.invyone.com` 접속 → `SubdomainResolverFilter` 가 Host 헤더 파싱 → `TenantRoutingDataSource``qnc_invyone` DB 로 자동 라우팅.
**전체 설명·플로우·배포 가이드는 반드시 읽을 것:**
- **`docs/MULTI_TENANCY_ARCHITECTURE.md`** ← 이거 하나만 읽으면 끝 (환경별 도메인 전략, CORS 패턴, 배포 체크리스트 포함)
### 절대 건드리면 안 되는 3가지
1. **테넌트 도메인에서 `NEXT_PUBLIC_API_URL=/api` 같은 Next rewrite 쓰지 말 것.**
Rewrite 가 Host 헤더를 변조해 `*.invyone.com` 서브도메인 파싱이 실패함.
`frontend/lib/api/client.ts` 의 "`.invyone.com` 이면 직접 `:8083/api`" 분기가 NEXT_PUBLIC_API_URL 체크보다 **앞에** 와야 함.
2. **CORS 는 `setAllowedOriginPatterns`** (`setAllowedOrigins` 아님). 와일드카드 매칭 필요.
**YAML 의 `[*]` 는 반드시 따옴표로 감쌀 것** (sequence 로 해석됨).
`CORS_ALLOWED_ORIGINS``.env` 에 있고 `.gitignore` 대상 → **서버마다 수동 세팅**.
3. **회사별 Hikari 풀은 반드시 `minIdle=0`.**
회사 N개 × minIdle=2 하면 Postgres `max_connections` 금방 초과.
`TenantDataSourceFactory.createTenant(...)` 만 사용할 것.
### 회사 생성 = API 호출 한 번
```
POST /api/admin/provisioning/companies
→ 6단계 자동 실행 (DB 생성 → pg_dump 스키마 복제 → 템플릿 데이터 → 관리자 → 메타 등록)
→ 13~15초 내 완료, 바로 서브도메인 접속 가능
```
필수 필드 4개만: `company_code`, `company_name`, `subdomain`, `db_prefix`.
나머지(사업자번호·대표자·이메일 등) 전부 optional.
### 환경별 도메인 — 요약
| 환경 | 접속 | 추가 설정 |
|---|---|---|
| **운영** | 실 `*.invyone.com` (+ 메인 `solution.invyone.com`) | **세팅 완료 (2026-04-24)** — Porkbun 와일드카드 DNS + Traefik v2.11 + Let's Encrypt DNS-01 와일드카드 TLS |
| **로컬 (직접 `docker up`)** | `*.localhost` 자동 | 0 (RFC 6761, Chrome/Firefox 기본 지원) |
| **원격 공유 개발서버** | `nip.io` or `hosts` 편집 | 경우 따라 |
### 새 환경 붙일 때 체크리스트
- [ ] `.env``CORS_ALLOWED_ORIGINS` 에 해당 도메인 패턴 포함 (`http://*.invyone.com:[*]` 등)
- [ ] DB 마이그레이션 `RUN_079/080/081.md` 운영 DB 에서 1회 실행
- [ ] `docker compose build --no-cache backend-spring` (`postgresql16-client` 포함)
- [ ] 프로덕션은 `tenant.provisioning.require-super-admin=true`
### 관련 코드 맵
```
backend-spring/src/main/java/com/erp/
├── tenant/ # 라우팅 (Filter / Holder / CompanyResolver / RoutingDataSource / Factory / DataSourceConfig / TenantController)
└── provisioning/ # 회사 생성 (Service / Registry / DatabaseCreator / SchemaCopier / DataCopier / AdminAccountCreator / Controller / StatsService)
backend-spring/src/main/resources/mapper/
├── tenant.xml # resolveDbNameBySubdomain
└── provisioning.xml # exists / insertCompanyWithTenant / listCompaniesForUi / updateDbStatus
frontend/
├── lib/api/provisioning.ts
├── lib/tenant/subdomain.ts # 호스트 파싱 + 예약어 제외
├── components/TenantGuard.tsx # 미등록 서브도메인 차단 (layout wrap)
├── components/admin/provisioning/ # 메인 화면 (accordion / stats strip / status dot / sparkline)
│ └── wizard/ # 4-step 마법사 (force_password_change 옵션 포함)
├── app/tenant-not-found/page.tsx # v5 solid+glow 에러 페이지
└── app/(main)/admin/sysMng/subdomainList/ # 회사 프로비저닝 페이지 엔트리
db/migrations/RUN_079/080/081_MIGRATION.md
notes/gbpark/2026-04-24-traefik-wildcard/ # 운영 Traefik 설정 산출물 (2026-04-24)
```