- ResponsiveDataView: 모바일 카드 뷰 (lg 미만) + 카드 스켈레톤에 scrollContainer 모드의 max-h + overflow-y-auto 적용. 데스크톱 테이블만 적용돼 있던 누락 픽스
- TableHistoryModal: timeline/detail 탭의 ScrollArea 고정 h-[500px] → 모바일 h-[300px] sm:h-[500px] 적응형. DialogContent max-h-[90vh] 와 충돌 방지
증상: 브라우저 width 좁아질 때 카드 그리드/이력 타임라인이 viewport 너머로 잘리고 스크롤 안 됨.
- Frontend: body 키를 snake_case (user_id/new_password) 로 변환
- Controller: new_password 도 추출해서 service 에 전달
- Service: 2-arg 오버로드 추가, newPassword 입력값 사용 (blank 일 때만 Welcome1! fallback), userId null/blank 시 IllegalArgumentException
증상: 사용자관리에서 비밀번호 초기화 modal 입력 → backend 가 user_id=null 로 SQL 실행 (0행) + newPassword 무시 후 항상 Welcome1! 로 덮어쓰기.
SubstituteSection 의 loadCandidates 가 res.data.list 를 가정했지만
getUserList(/api/admin/users) 응답은 { data: [...] } 형태 (data 가 list 자체).
결과로 모든 select 가 '지정 가능한 사용자가 없습니다' 로 표시됐음.
Array.isArray(res.data) 와 res.data.list 둘 다 fallback 으로 처리.
운영 QA 에서 발견된 3가지 결함을 한 번에 수정.
1. SubstituteController.java:56 / SubstituteService.java:242 (requireAdmin)
- role 비교에서 "COMPANY_ADMIN" 누락 → 운영 admin 이 대무자 지정 시 항상 403.
- 운영 회사 admin 의 user_type 은 COMPANY_ADMIN 이 표준 (AdminAccountCreator 가 그렇게 생성).
- "ADMIN" / "SUPER_ADMIN" 외 "COMPANY_ADMIN" 도 허용.
2. mapper/approval.xml (selectMyRequests, selectMyPendingLines)
- ORDER BY / SELECT 의 R.CREATED_DATE 가 잘못된 컬럼명 (APPROVAL_REQUESTS 실제: created_at).
- 결재함 /api/approval/my-pending, /api/approval/requests 가 항상 500.
- 3군데 R.CREATED_DATE → R.CREATED_AT.
3. SubstituteSection.tsx
- 대무자 ID 를 직접 타이핑하던 input 을 Select 로 교체.
- getUserList 로 같은 회사 활성 사용자 목록 로드, 본인 + SUPER_ADMIN + 비활성 자동 제외.
- 다이얼로그 열 때 한 번만 load (openDialog 시 loadCandidates).
- 빈 결과/로딩 placeholder 처리.
- frontend/lib/api/substitute.ts: 7개 API 함수 (Record<string, any> 컨벤션)
- components/admin/SubstituteSection.tsx (신규): 관리자용 대무자 지정 섹션
· 활성/예정 대무 관계 테이블, 사전 겹침 검증
· v5 토큰 (--v5-surface-solid, --v5-glow-sm) 사용, blur 금지
- components/admin/UserFormModal.tsx: 수정 모드일 때 SubstituteSection 노출
- components/layout/MySubstituteView.tsx (신규): ProfileModal 용 read-only 조회
· 내 대무자 + 내가 대무 중인 사람 양방향, D-day 카운트다운
- components/layout/ProfileModal.tsx: MySubstituteView 삽입
- app/(main)/approval/page.tsx: 대기함 행에 "대무 ← {원본 결재자}" 뱃지
· currentUser.user_id !== line.approver_id 비교 (별도 타입 필드 X)
페이지 외부 컨테이너를 h-screen 으로 가두고, 상단 4분할 카드는
viewport 비례(clamp 220~320px), 하단 메뉴 트리는 남은 공간을
flex-1 + min-h-0 으로 채우되 max-h(clamp 280~430px) 로 상한 제한.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SUPER_ADMIN cross-tenant 모드에서 menu API (/api/admin/menus) 가 500 응답을
내어 uiMenus 가 비어있고, 그 결과 우리 effect 가 매칭할 데이터가 없어
sessionStorage 의 영어 fallback title (deptMngList) 이 갱신되지 않던 문제.
AppLayout 의 fallback 두 곳에 ADMIN_PATH_LABELS 맵 추가:
1. URL 직접 진입 시 첫 openTab 의 fallback title
2. uiMenus 매칭 실패 시 한글 라벨 보강
근본 원인 (menu API 500) 은 별도 backend 이슈 — 본 fix 는 우회.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL 직접 진입 / sessionStorage 복원 시 AppLayout 의 fallback
(pathname.split('/').pop()) 이 path segment 를 그대로 탭 title 로
사용해서 '부서관리' 대신 'deptMngList' 같은 영어가 표시되던 문제.
- tabStore: updateTabTitle(tabId, title) 추가
- AppLayout: uiMenus 로드 후 admin 탭들의 admin_url 매칭하여
menu_name_kor (tabTitle/label/name) 로 갱신
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CRITICAL:
- searchUsers 회사/role 격리 가드 추가 (멀티테넌시 침해 차단)
- setPrimaryDept 멤버십 검증 추가 (주부서 데이터 손상 방지)
- parent_dept_code cross-tenant 검증 (validateParent 헬퍼)
HIGH:
- updateDepartment SQL WHERE 에 DELETED_AT IS NULL 추가 (silent corruption 방지)
- update/restore 부서명 중복 검증 추가
- 글로벌 부서 (*) write 작업 SUPER_ADMIN 전용 가드
- 부서코드 자동 생성으로 강제 (사용자 입력 받지 않음)
- 회사 변경 시 상세 패널 초기화
- handleMove 부분 실패 시 화면 동기화
- 검색 시 부모 체인 자동 포함 (broken tree 수정)
- start_date 기본값 today 강제 제거
MEDIUM:
- 멤버 fetch cancellation flag
- 삭제 다이얼로그 dept_code 클로저 캡처
- isDirty 시 X 버튼 폼 초기화 경고
- 변경이력 버튼 disabled (백엔드 API 미구현)
- 일괄등록 실패 상세 모달 (라인 + 사유)
- LIKE 와일드카드 ESCAPE 적용
- nullIfBlank 에 trim 통합
LOW + 새 기능:
- 부서원 추가/제거 UI 신규 구현 (UserSearchModal)
- selectDeptMembers LEFT JOIN 으로 변경
- DepartmentPicker allowRoot 옵션 (최상위로 이동)
- expandAll 전체 departments 사용
- dead code 정리
DB:
- RUN_085 마이그레이션: DEPT_INFO partial UNIQUE + USER_DEPT UNIQUE
- 모든 active 테넌트 DB (siflex/test01/test02_invyone) 적용 완료
Breaking changes:
- 일괄등록 CSV 4컬럼 → 3컬럼 (부서명,상위부서,유형)
- 부서코드 입력란 제거 (자동 부여 DEPT_n)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Docker Desktop on Windows 의 bind mount 가 host inotify 이벤트를
컨테이너로 전파하지 못해 Turbopack file watcher 가 host 편집을 감지 못 함.
webpack 은 WATCHPACK_POLLING=true 폴백을 지원하므로 Windows 에서만
Turbopack 을 끄고 webpack 으로 폴백 → 자동 HMR 복원.
- frontend/package.json: dev:docker:nopack 스크립트 추가 (next dev, no turbopack)
- docker/dev/docker-compose.windows.yml: Windows 전용 frontend command override
- scripts/start/invyone-start-docker-all.bat: windows.yml 자동 merge
Mac/Linux 진입점은 영향 없음 (start.bat 만 windows override 활성).
첫 컴파일은 약간 느려지지만 (~10-30%) 수정→반영 시간이 80s → 1~3s 로 단축.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cross-tenant fan-out 결과에서 회사 A·B 의 동일 objid 가 합본에 들어와
React key 중복 경고 발생 + isSelected 가 회사 구분 못 하던 문제.
- li key: role.objid → \`\${company_code}-\${objid}\` 조합으로 unique
- isSelected 비교: objid + company_code 둘 다 매칭
- selectedRole 유효성 체크(useEffect)에도 company_code 매칭 추가
추가:
- 메뉴 전체 트리구조에 자체 스크롤 (maxHeight: calc(100vh - 32rem))
- thead sticky top-0 + bg-muted (투명도 제거) → 스크롤 시 헤더 가려짐 해소
SUPER_ADMIN cross-tenant 정책 변경 없음 (모든 회사 합본 표시 유지),
React 식별만 정확해지는 fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
충돌 해결 5개 파일:
- .gitignore: .envrc/.direnv (hjjeong direnv 셋업) + .omc/ (gbpark) 양쪽 보존
- docs/MULTI_TENANCY_ARCHITECTURE.md: *.localhost dev 분기 + *.invyone.com/solution.invyone.com 통합
- frontend/lib/api/client.ts: 1-b *.localhost:8081 dev + 1-c DEV_TENANT_HOST(nip.io):8083 + invyone.com 신 도메인
- frontend/lib/tenant/subdomain.ts: IPv4 차단 + *.invyone.com + DEV_TENANT_HOST + *.localhost 모두 처리
- frontend/app/(auth)/login/page.tsx: B안 채택 — buttons 항상 렌더, className 만 mounted 가드 (next-themes 표준 패턴)
검증:
- backend: ./gradlew compileJava 성공 (Java 21)
- frontend: 머지된 4개 파일 관련 타입 에러 0개
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 각 zone 중앙에 SCADA 스타일 경고 비콘 자동 삽입 (펄스 링 + 빨간 배지 + 노란 경고 삼각형)
- WARN/ALARM 별 색상 분리 (CSS 변수 --b-* 로 SVG <use> shadow DOM 통과)
- WARN: 노란 톤 + 정적 표시
- ALARM: 빨간 톤 + drop-shadow + brightness 깜빡임
- zone-area 점선 테두리: warn(노란/얇음), alarm(빨간/굵음+pulse)
- ZONE 17 콘텐츠를 ZONE 18 비례에 맞춰 재배치 (label y 453→396) — manual-call 과 라벨 겹침 해소
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 건물 4종(utility/service/factory1/office)에 배경 그라디언트 분기
- zone 영역도 같은 톤 색상 클래스 (za-utility/service/factory1/office)
- room-label 폰트 5.7 → 7.5, zone-label 10 → 13.5 키움
- 센서/manual-call 아이콘 26 → 34, 18 → 24 로 확대 + 위치 보정
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 압력 게이지 1.5x 확대, PRESS/판독값/라벨 폰트 강조
- 모든 설비(탱크/펌프/시스템/MBR/필터/카본) 라벨 폰트 +2~4 단계 키움
- 센서/타일 박스 및 폰트 확대, 위치 간격 보정
- 위쪽 처리 라인 30px 위로 + TSS-RAW 자기 자리 유지하도록 dy 보정
- AIR BLOWER 를 CIP 패널 내려간 만큼 같이 내림
- 게이지/밸브/모듈/탱크 및 파이프 라우팅 미세 조정 (드래그로 잡은 좌표 일괄 반영)
- dev-drag.js: Shift+D 임시 드래그 모드. 컴포넌트/파이프 via/절대 끝점 핸들로 좌표 인스펙션 후 토폴로지 재빌드, 변경 이력 누적 패널 + 전체 복사
- 메인 모달 + alarm-list-dock 을 .emergency-stack 으로 묶어 viewport 안에서
flex 자동 분할 (어떤 해상도/줌에서도 dock 이 잘리지 않게)
- with-multi 진입 시 JS 가 stack 으로 동적 wrapping, 해제 시 원위치 복귀
- mini-modal 본문도 line-clamp 3 + min-height:0 로 길이 폭주 차단
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /fire-alarm 라우트에 invyone 브랜딩 적용된 모니터링 데모 추가
- CCTV 화면은 화재 감지 → 스프링쿨러 작동 2개 영상을 순차 재생
(단일 합본 mp4 의 ended 이벤트 불안정 + 루프 이슈로 분리)
- v1 종료 시점에 자동 phase2 전환: 스프링쿨러/방화셔터 상태 활성화
- 정보 패널 스프링쿨러 상태 텍스트에 시안 글로우 펄스로 작동 표시
- 종료 검출 3중 방어 (timeupdate / ended / hard-stop timer)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 스텝 사이 화살표에 "병렬로 합치기" 버튼 추가 → 두 스텝을 같은 execution_order 로 묶음
- 병렬 스텝 카드별 분리 버튼 (trash 왼쪽) → 해당 카드만 다음 스텝으로 빼냄
- 병렬 스텝 헤더 amber 배경 + ⚡ "병렬 실행" 배지로 시각 차별화
- (순차)/(병렬) 부가 라벨 제거, 깔끔한 "Step N" 표기로 통일
- execution_order 정규화 헬퍼(renormalizeOrders) + 일괄 저장(persistOrders) 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- apple-mobile-web-app-capable + status-bar-style: iOS Safari 의 홈 화면 추가 시 standalone (주소창 + 하단바 제거)
- viewportFit:cover: 노치/홈바 영역까지 사용
- userScalable:false + maximumScale:1: 시연 중 핀치 줌 방지
- themeColor: 알람 모드 배경(#0d0202)과 일치
iOS PT 폰: Safari 공유 → 홈 화면에 추가 → 풀스크린 standalone.
Android PT 폰일 경우: manifest.json + icon 추가 별도 필요 (다음 단계).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
데스크톱이 /scada?worker=siflex_user query 매번 안 박아도 자동으로 작업자 폰(siflex_user)에 push.
다른 작업자 시험할 일 있으면 ?worker=<id> 로 override 여전히 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- frontend/components/layout/MenuItemActions.tsx — AppLayout/TopNavBar 가 import 하는데 빠져서 webpack 빌드 fail
- backend-spring db migration V001 (varchar_migration) + V002 (create_missing_tables) 같이 누락분 정리
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 음성 인식 (scada-demo/js/voice.js) — 한국어 발화 → 키워드 매핑 → INVYONE_UI.select()
· 사이드바 마이크 버튼 + transcript 라벨, 매칭 시 청록 펄스
· Chrome/Edge HTTPS 환경 (운영 siflex.invyone.com OK)
- 경고시스템/다중경고 버튼을 음성 인식과 동일 톤
· 🚨 emoji → SVG 삼각형 아이콘, voice-btn 패턴 (다크 솔리드 + 컬러 액센트)
· 정적 (반짝 펄스 애니메이션 제거)
- client.ts stash pop conflict 정리 (DEV_TENANT_HOST + 도메인 정리 통합)
- ui.js 다중 경고 시연 wiring + scada 작업 노트 2건
- 기타 syncthing 보류분 batch (대시보드/레이아웃/로그인 layout 정리)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 작업자 폰(/mobile)을 SCADA 데모와 ws 로 연결, 알람 발생 시 풀스크린 푸시
· v5 솔리드+글로우 톤, 진동/Web Audio 비프/Wake Lock/auto reconnect
· 시연 안전망: ?test=1 자동 발동, 우상단 hidden 트리거
- backend: com.erp.alarm 신규 패키지 (WebSocketConfig + Handshake + Handler + Controller)
· JWT 토큰 핸드셰이크 검증, userId 기반 채널 매핑 (멀티 디바이스 지원)
· spring-boot-starter-websocket 의존성 추가
· path 를 /api/demo/* 안에 두어 Traefik 라우트 추가 불필요 + 정식 알람과 분리
- SCADA scenario.js 의 emergency 시퀀스(2700ms)에 fetch('/api/demo/alarm/trigger') 배선
· /scada?worker=<user_id> query 로 target user 지정 (iframe src 로 전달)
- 운영 시연 URL: siflex.invyone.com/mobile (siflex_user) ↔ /scada?worker=siflex_user
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
백엔드:
- 6개 AI Service (group/apiKey/provider/conversation/agent/scheduler) 가 응답 메서드에서
`parseJsonField` 헬퍼로 JSONB(::text) 컬럼 (connectors / config / permissions /
metadata / tool_calls / notification / tools) 을 String → Object 자동 변환.
- 모범 패턴 (`AuditLogService.processChanges`, `BusinessRuleService.parseJsonField`,
`DataflowDiagramService.parseJsonbFields`) 동일하게 적용.
- model 의 String getter 는 그대로 유지 — `MultiAgentExecutionEngine` 등
내부 LLM 호출 chain 영향 없음 (`getEntityById` 분리).
- 컨트롤러 시그니처 generic 만 변경 (return type Map).
프론트엔드:
- `safeArray<T>` / `safeObject<T>` 헬퍼 (`lib/utils.ts`) — 백엔드가 미파싱 String 으로
올 때 graceful fallback. 빈 배열/객체 반환.
- `workspace/page.tsx` 멤버 카드:
- `safeArray(member.connectors)` 적용 → `.map()` 폭발 차단.
- 좁은 viewport 에서 한글 텍스트 한 글자씩 세로로 깨지던 문제 해결
(`flex-wrap` + `truncate` + `whitespace-nowrap` + `max-w` + `title`).
그렘린 1000마리 폭격 + architect 자문으로 발견. workspace `Application error`,
`memberConnectors.map is not a function` 모두 해결.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
description 유무에 따라 카드 내부 컨텐츠 높이가 달라져 모델 배지와 설정 버튼이 카드마다 다른 높이에 노출되는 문제.
- 카드를 flex column 으로 변환하고 버튼 행에 mt-auto 적용 → 항상 카드 바닥 고정.
- description 영역을 항상 렌더링하고 min-h-[28px] (~2줄) 로 고정 → 빈 description 카드도 동일한 공간 차지 → 모델 배지 위치도 일치.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 토글 컨테이너/버튼에 shrink-0 + whitespace-nowrap 추가
- 헤더 행을 flex-wrap 으로 보강해 좁은 폭에서 우아하게 줄바꿈
- raw <button> 이라 shadcn Button 의 nowrap 보호 막이 없어
CJK 음절 단위로 "병/렬", "순/차", "혼/합" 으로 끊기던 문제 해결
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 와이어프레임 박스(테두리 + 투명 fill) 대신 실제 TemplateRenderer
를 mock empty context 로 띄워 transform: scale 로 축소 → 사용자가
빌더에서 그린 그대로의 레이아웃이 카드에서 그대로 읽힘.
- TemplateMiniPreview 컴포넌트 신설:
- DEFAULT empty context (data:[], callbacks no-op) 로 데이터 fetch 0회
- BASE_WIDTH=1200, 16:10 stage → ResizeObserver 로 카드 폭 변화 자동 추종
- pointer-events: none / user-select: none / overflow: hidden
- views 가 비어있으면 기존 TemplateThumbnail (와이어프레임) 폴백
- TemplateLibraryModal 카드 아이콘 자리 교체
- dashboard.css 에 .dash-lib-card-thumb--live / -stage 추가
향후 템플릿 50+ 로 늘어 모달 첫 오픈이 무거워지면 lazy mount(
intersection observer) 또는 background 스크린샷 캐싱으로 전환.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: 컴포넌트마다 .55 alpha 솔리드 박스 → 화면 스크린샷처럼 보임
변경: 5px inset 패딩 + 각 블록은 .55 테두리 + .14 fill 의 와이어프레임 톤
- KIND_COLOR (rgba 문자열) → KIND_RGB (RGB 트리플 문자열) 로 변경
→ 같은 색을 테두리/배경 다른 알파로 동시 사용 가능
- thumb 안에 dash-lib-card-thumb-canvas wrapper 추가해 padding 적용
- block 은 border-box + border 1px transparent base, 색상은 inline 으로
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 fromV2 / fromV1 은 BlockV2.xPct 또는 TemplateComponent.order/row
가정이었는데, 실 운영 빌더(templateAdapter.saveTemplate) 는
list 가 컴포넌트 배열이고 각 요소가 position{x,y} + size{w,h} 절대 px,
공통 screenResolution 으로 정규화되는 구조였음.
fromStudio 추가:
- list 를 배열 / v2.1 layers / { components } 어느 모양이든 평탄화
- screenResolution 우선, 없으면 컴포넌트 bounding box 폴백
- url(v2-table-list 등)/componentType/widgetType 어느 키든 inferKind 로 분류
호출 순서: fromStudio → fromV2 → fromV1 (안전망).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
템플릿 목록 카드의 정적 📋 아이콘을 실제 view 구조 기반의
미니 와이어프레임으로 교체. 사용자가 카드만 보고도 템플릿이
어떤 화면인지(테이블 위주 / 폼 위주 / 단순 버튼 등) 파악 가능.
- backend: getTemplateList SQL 에 VIEWS 컬럼 추가, list 응답 각
row 의 views jsonb 를 객체로 파싱
- frontend: TemplateThumbnail 컴포넌트 신설 — v2(BlockV2.xPct/yPct
/wPct/hPct) 정규화 좌표 우선, v1(order/row) 폴백, 컴포넌트
종류별 색상(table=primary, form=cyan, button=pink)
- TemplateLibraryModal 카드 아이콘 자리 교체
- dashboard.css 에 .dash-lib-card-thumb / -block 스타일 추가
(v5 토큰 준수 — solid + glow, blur 없음)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
setSettingsOpen(true) → setSettingsOpen(v => !v) 로 변경.
열려 있을 때 한 번 더 누르면 SettingsModal 이 닫히고,
v5-hdr-icon.on 활성 상태도 자동 토글됨.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: AI 어시스턴트 → '에이전트 관리' 클릭 후 '멀티에이전트 워크스페이스'
이동하면 사이드바에 두 메뉴가 동시에 보라색 active 로 표시됨.
원인: isMenuActive 가 pathname 매칭과 activeTab 매칭을 OR 결합해서
이전 URL 메뉴 + 현재 활성 탭 메뉴 둘 다 true 반환.
수정: activeTab 이 있을 때는 그 탭 기준으로만 매칭. activeTab 이 없는
경우(/main 첫 진입 등)에만 pathname 으로 fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 권한있는/없는 직원 영역의 체크박스를 클릭하면 즉시 다시 해제됨
처럼 보이고 (사실은 두 번 토글됨), 체크 상태 자체가 유지 안 됨.
전체선택 버튼 (별개 핸들러) 만 정상 동작.
원인: <li onClick={토글}><Checkbox onCheckedChange={토글} /></li> 구조에서
체크박스 클릭 → onCheckedChange 트리거 (토글 1회) → 이벤트가 li 로 버블 →
li onClick 트리거 (토글 1회 더) → 합쳐서 2회 토글 = 원상복귀.
수정: Checkbox 에 onClick={(e) => e.stopPropagation()} 추가해서 li 로
버블 차단. li 빈 영역 클릭 시는 li onClick 만 동작, 체크박스 직접 클릭
시는 onCheckedChange 만 동작 — 둘 다 정확히 1회 토글.
권한있는 직원 + 권한없는 직원 두 곳 모두 수정.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 검증 중 발견. SUPER_ADMIN 이 admin 도메인에서 회사 필터를 "시연용회사2"
선택해도 TEST01 의 권한 그룹이 그대로 보이던 UI 버그.
원인: roleAPI.getList 의 cross-tenant 분기는 fan-out 으로 모든 회사 그룹을
모아 응답에 company_code 박아 돌려줌. 화면이 그 결과를 그대로 렌더하면서
selectedCompany dropdown 으로 client-side 필터링을 안 했음.
수정: rolesList 의 filteredRoleGroups 가 isSuperAdmin && selectedCompany!="all"
조건일 때 company_code 일치 행만 통과시키도록 한 줄 추가.
알려진 이슈 (별개, Phase 2 후속):
- GET /api/admin/cross-tenant/roles/{id}/workspace 가 500 떨어짐.
단일 모드 GET /api/roles/{id}/workspace 도 group=null 빈 결과 — 사전
존재 시드/스키마 mismatch 추정 (RoleService.getRoleWorkspace 의 5개 mapper
중 하나가 깨짐). cross-tenant 분기 자체 (runInCompany) 는 정상.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>