


여러 플랫폼용 UI 라이브러리를 패키지 계층을 깨지 않고 유지 관리하기
여러 플랫폼용으로 계층별 패키지로 나뉜 UI 라이브러리에서 작업할 때 사용합니다. 패키지 계층과 공개된 export를 지키고, 외부 서비스 어댑터를 정해진 파일 구성으로 만들며, 릴리스·리뷰 규칙을 따릅니다.
이 AI 규칙을 사용하려면
## 각 코드를 어느 패키지 계층에 둘지 정하기
코드베이스를 각각 하나의 책임만 지는 작은 레이어 스택으로 구성한다: 프레임워크 훅을 헤드리스 리소스로 실행하는 반응형 프리미티브 레이어, 그 프리미티브를 프레임워크의 상태 시스템과 연결하는 바인딩 레이어, 도메인 로직을 소유하는 프레임워크 비종속 코어 런타임, 그리고 코어를 재내보내고 플랫폼별 UI 프리미티브를 추가하는 대상 플랫폼(웹, 네이티브, 터미널)별 배포 패키지 하나씩. 비공개 컴포넌트 킷은 런타임 의존성으로 배포되는 대신 소비자 프로젝트로 복사되는 표준 UI 소스를 담는다. 공유 빌드 도구는 자체 내부 패키지에 둔다.
배치 규칙:
- 프레임워크 비종속 로직은 코어 패키지에 속한다. 플랫폼에 결합된 런타임은 배포 패키지에 속한다.
- 프레임워크에 결합된 런타임은 모든 배포 패키지가 재내보내는 코어 패키지의 서브 경로에 위치시켜, 플랫폼별로 복사본을 두는 대신 구현을 하나만 둔다.
- 소비자는 코어 패키지를 직접 설치하지 않는다. 배포 패키지 중 정확히 하나만 설치한다.
- 외부 프로바이더를 코어 런타임에 매핑하는 통합 패키지는 그 자체로 별도 레이어이며, 코어 로직을 절대 포크하지 않는다.
## 공개 export는 추가만 하고 이미 배포된 것은 없애지 않기
배포되는 모든 패키지의 공개 표면은 추가 전용(append-only)이다. 심볼이 이동하면 그 export를 새 파일로 다시 가리키게 하고, 이미 배포된 export는 절대 삭제하지 않는다. 저장소 내부 감사만으로는 다운스트림 소비자를 볼 수 없으므로, 겉보기에 사용되지 않는 타입 하나를 제거하는 것조차 호환성 파괴 변경이 된다. 실제 동작 변경은 이동이나 이름 변경 속에 숨기지 말고 항상 그 자체로 별도의 의도적인 변경으로 배포한다.
장기 마이그레이션이 레거시 구현을 새 아키텍처로 대체할 때, 패키지 진입점은 마이그레이션 기간 동안 두 경로를 모두 재내보내며 여전히 추가 전용을 유지한다. 이러한 마이그레이션 중에는:
- 새 동작은 표준(canonical) 표면에만 추가하고, 폐기 예정 경로에는 절대 추가하지 않는다.
- 통합별로 사본을 새로 작성하는 대신 공유 코어 프리미티브를 재사용한다.
- 또 다른 옵트아웃 플래그를 도입하기보다 재사용을 우선한다.
## 외부 서비스 어댑터를 표준 파일 구성으로 만들기
통합 어댑터는 하나의 진입 훅, 일련의 접근자(accessor) 훅, 순수 변환기(converter)를 통해 외부 프로바이더를 코어 런타임에 매핑한다. 지배적인 경험칙: 런타임 파일은 오케스트레이션을 담당하고, 순수 모듈은 변환을 담당하고, 컨트롤러(있는 경우)는 리듀스를 담당하며, 훅은 읽기만 한다.
- 코어 런타임 선택. 메시지가 프로바이더가 소유한 소스에서 파생되는 경우에는 외부 스토어 런타임 위에 구축하고, 프로바이더가 스레드 상태를 전혀 보유하지 않는 경우에는 로컬 런타임과 채팅 모델 어댑터를 조합해 구축한다. 스레드 목록이 필요할 때는 이 둘을 공유 멀티스레드 런타임으로 감싼다. 배포된 런타임 코어를 재사용하고, 병렬 스레드 런타임 상태 보관소를 직접 손으로 만들지 않는다.
- 상태 노출. 런타임 상태는 손으로 만든 고유 심볼 브랜드·가드가 아니라 공유 런타임-엑스트라 팩토리를 통해 접근자 훅에 노출한다.
- 표준 파일 구성. 오케스트레이션 전용 런타임 진입 파일 1개, 런타임-엑스트라 인스턴스 1개, 접근자·액션 훅 파일 1개, 양방향 순수 메시지 변환기 파일 1개, 타입 파일 1개. 어댑터가 스레드 상태를 소유하는 경우에는 스레드 컨트롤러와 순수 상태 리듀서를 추가하고, 프로토콜이 와이어 포맷을 소유하는 경우에는 별도의 서버 또는 node 서브 경로 진입점을 추가한다.
- 서버 전용 코드. 서버 전용 코드와 프로바이더 SDK 코드는 서버 또는 node 서브 경로 안에만 두고, 기본 진입점과 네이티브 진입점 밖에 둔다.
- 테스트. 각 모듈 옆에 테스트를 같이 두고, 변환기는 양방향 모두, 리듀서 또는 컨트롤러, 그리고 모든 접근자 훅을 커버한다.
프로바이더 주도로 유연하게 정할 수 있는 항목: 어떤 코어 프리미티브 위에 구축할지, 얇은 래퍼로 할지 어큐뮬레이터로 할지 컨트롤러로 할지, 맞춤형 트랜스포트를 쓸지, 휴먼인더루프 지원을 어느 정도 풍부하게 할지, 스레드 목록 지원을 어느 깊이까지 갈지.
## 어댑터끼리 기능을 맞추고 변환기가 예외를 던지지 않게 하기
런타임 간 동등성(parity). 어댑터 간 기능 수준을 동등하게 유지한다. 어떤 기능이 한 런타임에 추가되면, 그 개념이 적용되는 모든 런타임에서 동일하게 노출하거나, 왜 하나만 다르게 두는지 기록해둔다. 조용히 방치된 동등성 격차는 어댑터를 바꾸는 사람에게 예상치 못한 파손이 된다.
방어적 변환기. 변환기와 콘텐츠 블록 렌더러는 정의되지 않은 필드, 누락된 필드, 사용 불가능한 플랫폼 기능에 대해 절대 예외를 던지지 않아야 한다. 스펙을 벗어난 프로바이더 페이로드에 대비하고, 브라우저 전용 플랫폼 API는 서버·터미널 렌더러·네이티브 타깃에서도 실행되는 코드 경로에 섞이지 않게 한다.
절대 도입하면 안 되는 패턴:
- 배포된 런타임 코어를 중복하는 병렬 스레드 런타임 상태 보관소.
- 비반응형 상태를 컴포넌트 트리에 연결하기 위한, 변경 알림과 버전 카운터를 이용한 재렌더링 편법.
- 프로토타입 생성을 통해 객체에 메서드를 이식하는 것.
- 호출자가 소유한 객체에 대한 몽키패칭.
## 공용 빌드 도구로 빌드하고 릴리스 노트 추가하기
배포 가능한 모든 패키지는 공유 빌드 도구를 통해 빌드한다. 패키지별 빌드 설정을 추가하거나 대체 번들러 또는 컴파일러 CLI를 직접 사용하지 않는다. Export 맵은 모듈 전용이며 타입 우선(types-first)으로, 타입 선언을 기본(default) 조건보다 앞에 나열하고, 패키지는 스스로를 부작용 없음(side-effect free)으로 선언한다.
- 표준 UI 소스 디렉터리는 하나만 둔다. 템플릿과 예제는 컴파일러 경로 매핑을 통해 이를 별칭으로 참조하며, 미니멀 스타터가 의도적으로 자체 사본을 배포하는 경우를 제외하고는 바이트 단위로 동일한 중복본을 두지 않는다. 동기화 스크립트가 그 사본을 바이트 단위로 동일하게 유지하며, 의도된 모든 차이는 스크립트의 오버라이드 목록에 명시한다.
- 디렉터리 형태 규칙은 의도에 따라 다르다: 모든 프리미티브가 두 형태 모두 존재할 때는 병렬 디렉터리를, 일부 파일만 두 번째 형태를 가질 때는 접미사 없는 파일을 기본 소스로 둔 채 희소한(variant) 접미사 방식을 쓴다.
- 컴파일러 툴체인이나 그 플러그인을 올릴 때는 컴파일러 플러그인 검증 작업을 다시 실행한다. 빌드가 그린이라는 사실만으로는 툴체인이 여전히 온전하다는 증거가 되지 않는다.
- 배포된 번들이 런타임에 임포트하는 패키지는 반드시 런타임 의존성에 두고 개발 의존성에 두지 않아야, 번들러가 이를 외부(external)로 취급한다. 잘못 배치하면 패키지가 인라인되어 해결 불가능한 전이 임포트가 소비자 빌드에 딸려 들어간다.
- 복사 설치형 컴포넌트 진입점은 자기완결적이어야 한다: 내부 컴포넌트 임포트와 스타일시트 임포트를 모두 레지스트리 의존성으로 열거하여, 설치기가 해결 불가능한 임포트를 가진 파일을 절대 남기지 않게 한다.
- 배포된 패키지에 대한 모든 변경에는 릴리스 노트 항목을 첨부한다. 항상 패치 버전 올림을 사용하며, 마이너·메이저 버전 올림은 메인테이너 승인이 필요하다. 비공개 패키지는 예외이다.
## 자동 생성된 API 레퍼런스를 손으로 고치지 않기
생성된 API 레퍼런스 트리 전체는 손으로가 아니라 그 생성 스크립트가 소유한다.
- 전체 페이지 생성 마커가 붙은 페이지는 전부 생성기가 소유한다. 생성된 블록 안의 수동 편집은 다음 실행 시 소실되므로, 오래 남아야 하는 서술문은 소스 문서 주석에 대신 넣는다.
- 재생성 후에도 살아남아야 하는 수기 콘텐츠는 지정된 수동(manual) 또는 예제 슬롯에만 넣는다.
- 완전히 수동으로 관리되는 페이지는 프런트매터 바로 뒤에 자동 생성 제외(skip-auto-generation) 마커를 붙여야 한다. 이 마커가 있어야 페이지가 섹션 내비게이션에 계속 표시되고 정리(pruning) 대상에서 제외된다. 없으면 예상치 못한 비생성 페이지로 간주되어 미관리·구식으로 보고되며 엄격 모드가 실패한다.
- 그 트리 아래의 섹션 및 루트 내비게이션 메타데이터 파일도 생성된 것이다. 절대 손으로 편집하지 않는다.
- 지속적 통합(CI)은 엄격 모드로 재생성하며 어떤 드리프트에도 실패하므로, 제출 전에 로컬에서 먼저 재생성한다.
## 스타일은 린터와 포매터에 맡기고 주석은 최소로 쓰기
저장소의 린터와, 기계적인 문제에 대해서는 그 자동수정(autofix) 모드를 실행한다. 포맷팅은 포매터가 전담하므로 절대 손으로 포맷하지 않으며, 리뷰에서 공백이나 포맷 관련 지적을 절대 제기하지 않는다.
반응형 리소스는 프레임워크 훅 위에 구축되므로, 의존성 배열과 훅 순서 규칙은 린터의 네이티브 훅 규칙으로 강제된다. 이 규칙들이 함수 본문을 분석하려면, 프레임워크 도구가 훅으로 인식하도록 함수에 이름이 있어야 한다: 리소스를 훅 접두사가 붙은 함수로 추출해 리소스 팩토리에 전달하고, 루트 생성 헬퍼에는 화살표 함수 대신 이름 있는 함수 표현식을 준다.
기본값은 코드 주석 0개다. 코드를 그대로 되풀이하거나, 이력을 기록하거나, 현재 변경 요청을 가리키는 주석은 모두 삭제한다. 숨겨진 불변조건, 비자명한 제약, 상위 시스템에 대한 우회책처럼 코드 자체만으로는 미래의 어떤 독자도 복원할 수 없는 이유를 문서화하는 경우에만 주석을 남기며, 중립적인 평서문으로 작성한다.
## 테스트는 모듈 옆에 두고 스냅샷은 쓰지 않기
테스트는 대상 모듈 옆에 두고, 패키지 이름을 통해서가 아니라 상대 경로로 임포트하여, 테스트가 항상 해석된 빌드 산출물이 아니라 로컬 소스를 실행하도록 한다.
- 변환기는 양방향 모두, 리듀서 또는 컨트롤러, 그리고 각 접근자 훅을 각자의 테스트 파일에서 커버한다.
- 테스트 러너의 호이스트된 목(mock) 헬퍼로 목을 구축하고, 손대지 않은 export가 실제 동작을 유지하도록 원본 모듈을 항상 목에 스프레드해 넣는다.
- 스냅샷 단언(assertion)은 사용하지 않는다. 실패가 실제 회귀를 정확히 짚어내도록 중요한 구체적 값들에 대해 단언한다.
## 변경은 한 가지 목적으로, 설명과 재현 방법을 붙여 제출하기
- 변경 요청 하나당 관심사 하나. 크거나 여러 목적을 가진 변경은 각각 리뷰하기에 충분히 작고, 각자의 근거로 승인받을 수 있고, 회귀 시 깔끔하게 되돌릴 수 있도록 별도 제출로 나눈다.
- 자동 생성된 자리 표시자가 아니라 실제 설명을 작성한다. 변경 내용이 무엇인지, 왜 필요한지, 어떻게 이루어졌는지를 서술하여 리뷰어가 diff를 역공학하지 않고도 충분한 맥락을 갖도록 한다. 제목은 변경 내용을 한 줄로 명명하고, 트레이드오프와 기존 방식과의 차이는 본문에 둔다.
- 기존 저장소 관행과의 모든 차이를 정당화한다. 코드베이스가 이미 어떤 방식으로 문제를 해결하고 있다면 그것을 따른다. 누군가 그 방식을 의도적으로 선택한 것이므로, 다른 방식을 쓰려면 개인적 취향이 아니라 문서화된 이유가 필요하다.
- 관련 없는 변경은 diff에서 배제한다. 지나가는 김에 하는 삭제, 리팩터링, 재포맷, "온 김에" 추가는 진짜 변경을 파묻고 리뷰를 늦춘다. 별도 제출을 열거나, 단지 문제를 짚고 싶을 뿐이라면 이슈를 연다.
- 사소하지 않은 기능 변경 전에는 이슈를 먼저 연다. 한 단락이면 상당한 시간을 들이기 전에 메인테이너가 방향이 맞는지 확인할 수 있다. 오탈자나 작은 문서 수정 같은 사소한 수정에는 이슈가 필요 없다.
- 최소 재현 사례를 첨부한다. 모든 결함 보고나 수정 제출에는 정확한 버전에서 실행 가능한 재현 사례가 필요하다. 메인테이너가 보고된 동작을 재현할 수 없으면 그 보고는 닫힌다.
- 수정이 근본 원인을 다루는지 확인한다. 변경을 적용하면 보고된 실패가 사라지고 되돌리면 다시 나타나는지 검증하고, 그 메커니즘을 설명한다. 값을 바꾸거나, 기능을 제거하거나, 타입을 넓혀서 증상만 가리는 변경은 거부된다.
- 문서와 생성된 레퍼런스를 코드와 같은 제출 안에서 항상 맞춰 둔다. 레퍼런스를 재생성하고, 변경으로 인해 내용이 무효화된 모든 페이지를 업데이트한다. 자동화는 API가 조용히 사라지거나 나중 변경으로 미뤄지게 두지 말고, 문서 드리프트가 있으면 실패해야 한다.
## 프레임워크 기능 재구현처럼 리뷰에서 거절되는 변경 피하기
- 프레임워크가 이미 제공하는 것을 재발명하는 것. 런타임 상태, 반응성, 기타 배선(plumbing)을 재구현하는 대신 공유 프리미티브 위에 구축한다. 병렬 메커니즘은 나머지 코드베이스에서 점점 벗어나며 모두에게 유지보수 비용을 지운다.
- 무겁거나 오픈소스가 아닌 의존성을 가볍게 추가하는 것. 상업적으로 라이선스된 라이브러리는 오픈소스 프로젝트에서 사용 불가이며, 무거운 의존성이나 새로운 빌드 단계는 모든 기여자에게 영향을 미치므로 정당화와 메인테이너 승인이 필요하다.
- 기존 예제와 중복되는 새 예제 애플리케이션을 배포하는 것. 먼저 기존 예제를 훑어본다. 이미 해당 통합을 다루는 예제가 있다면, 거의 동일한 예제를 추가해도 얻는 것이 없으며, 메인테이너가 요청하지 않는 한 별도의 개인 저장소에 두어야 한다.
- 사용자 경험 회귀를 도입하는 방식으로 결함을 고치는 것. 기능을 비활성화하거나, 애니메이션을 제거하거나, 렌더링·떨림 결함을 가리기 위해 인터페이스를 넓히는 것은, 원래 보고가 사실이더라도 받아들일 수 있는 수정이 아니다.
- 툴체인이 이미 강제하고 있는 방어적 검사를 추가하는 것. 정확한 선택적 프로퍼티 검사를 포함한 가장 엄격한 컴파일러 설정 하에서는, 널가능성·선택적 프로퍼티 가드는 중복이다.
- 머지를 위해 필수 체크를 무시하는 것. 체크 목록의 모든 항목이 통과했거나 명시적으로 건너뛴 것으로 표시될 때까지 강제 머지하지 않는다. 통과·건너뜀 항목을 걸러내고 남는 것이 없는지 확인한다. 잘려 나온 목록이 실패한 체크를 숨긴 적이 있기 때문이다. 저장소 고유의 체크가 실패했거나 대기 중이면, 무시하지 말고 해결한다.
## 같은 작업 트리에서 다른 에이전트의 변경을 덮어쓰지 않기
다른 자동화 에이전트와 사람이 동시에 같은 작업 트리를 편집하고 있다고 가정한다. 편집 전에 현재 작업 트리 상태를 확인하고, 자신이 만들지 않은 변경을 덮어쓰지 않는다. 각 변경의 범위를 좁게 유지하여 동시 진행되는 작업이 깔끔하게 분리될 수 있도록 한다.
프로젝트의 버전 관리 워크플로를 존중하며, 원시 버전 관리 명령이 적절하다고 함부로 가정하지 않는다. 작업 브랜치가 프로젝트를 버전 관리 위에 얹힌 워크플로 도구로 관리하고 있음을 나타낼 때는, 명시적으로 요청받지 않는 한 직접 명령으로 브랜치를 생성하거나, 파일을 스테이징하거나, 커밋하거나, 이력을 재작성하지 않는다.
