공용 controller에서 기능별 binding으로 — A15 shell 책임 정리 (2026-09-10)
- 기간: 2026-09-10, 한 작업. 오너 지시: “바벨릭 #1531 진행해줘. 나한테 물어보지 말고 phase 끝까지 완주”.
- 랜딩: 앱 PR #1550 → release/v0.18.0, merge
290eadb9b808cbbc85d69fde09600e79b925ee9c, 2026-09-10 20:39:38 KST. 문서 PR #57. DB·마이그레이션·Edge·배포 변경 없음. staging/Production 미승격. - 설계서: #1531 실행계획, 채택/기각과 예상 효과 포함.
- 정본: feature 통합 §10, 앱 src/react/features와 app.tsx/appController.tsx/양 플랫폼 root.
- 도구: 앱 tests/browser/a15·u02·u03, 기존 node test/타입/경계 검사. work의 추출·분류·mutation 스크립트는 임시 산출물이며 앱 실행 코드가 아니다.
- 게이트: Phase별 npm run check, A15 실제 Chromium 7개, U02 22개, U03 25개+실제 browser zoom 4개. 최신 release 00a93ad3 실제 통합 후 precheck 2분24초·3,579 pass/0 fail/88 조건부 skip, 정적·미사용·앱 빌드·산출물 통과. Merge Check 성공·release HEAD/merge ancestry 확인. 일반 Merge Check이며 full CI 성공을 뜻하지 않는다. DB 변경이 없어 이번 작업의 별도 pgTAP 실행은 없음.
- 버그리포트: 없음. 운영 장애를 새로 단정하지 않은 구조 이식 작업이다. owner 정리·늦은 응답은 회귀 경계로 확인했다.
- 계약: feature-resource-integration §10, 소유권 장부 A15 증거.
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 선행 7개 포함·실제 책임/기준선 | 완료 · 기준 b0fa93ec |
| Phase 2 | 기능별 상태·명령·구독·화면 이전 | 완료 · 151217fd |
| Phase 3 | 공용 context·재수출·root 정책 제거 | 완료 · eb17ad1c |
| Phase 4 | 실제 화면·편집·수명주기 검증과 문서 인계 | 완료 · 2147101c, precheck 정리 7aebc993 |
네 Phase를 앱 PR #1550 하나로 통합했다. 각 Phase check와 실제 브라우저 증거를 보존하고, 최신 release를 합친 최종 커밋 7aebc993에서 필수 precheck를 통과했다. 큐는 검사한 merge 290eadb9를 그대로 반영했다.
1. 배경
홈에서 기록 상세를 열고 수정한 뒤 돌아오는 기능은 owner resource와 공통 편집기로 이미 분리돼 있었다. 그런데 화면 연결은 appController 2,650줄·mobileApp 2,213줄·desktopApp 1,193줄과 하나의 넓은 context가 계속 맡았다. 직접 선행 A10/A11/A12/A13/A14/U02/U03의 최종 PR가 착수 release에 포함됨을 확인했다.
2. 문제 제기
한 화면 변경이 공용 전달 객체와 양 플랫폼 root에 전파됐다
실제 전달 필드는 계획의 197개보다 늘어난 206개였다. 운동 진입·카탈로그·직접 기록·온보딩·달력 선택·표시 데이터와 UI 구독이 root에 함께 있었다. 파일 줄 수보다 이 책임 혼합이 문제였다. Production 공개 페이지 HTTP 200은 확인했지만 로그인 사용자 성능이나 운영 장애의 재현으로 보지 않았다.
3. 해결 방안
원칙
D1(2026-09-10): 기존 owner resource·editor·명령을 재사용해 모든 Phase와 release 통합까지 진행한다. 전역 event bus나 service locator를 만들지 않는다.
접근
| 방안 | 판단 |
|---|---|
| 기능별 명시적 계약과 실제 hook/selector/command 소유 이전 | 채택. feature의 변경 위치와 해제 책임이 일치한다. |
| 공용 context를 여러 중첩 객체로 다시 포장 | 기각. 정책·구독을 root에 남긴다. |
| 범용 등록기/전역 event bus | 기각. 의존성과 해제 순서를 숨긴다. |
4. 적용한 내용
Phase 1 — 실제 기준선
착수 release b0fa93ec에서 check 3,572 pass/0 fail/70 조건부 skip. 기존 원본·편집·owner 계약과 직접 선행 SHA를 고정했다.
Phase 2 — 기능별 실제 소유 이전
active/completed workout·plan·calendar·home·catalog·PR/volume·profile/onboarding/import·social/group binding으로 이전했다. 플랫폼 root는 구독을 개별 binding에 넘기고 route/overlay를 조립한다. store/runtime를 중복 생성하지 않는다. 직접 기록은 owner 전환 때 비우고 늦은 답장을 거부한다. 초안 lifecycle은 준비된 owner만 복원하고 background listener를 해제한다.
Phase 3 — 공용 전달 경로 폐기
renderCtx·ControllerRenderContext와 테스트 전용 재수출을 삭제했다. app children은 ReactNode이며 각 binding은 전체 controller 타입을 가져오지 않는다. root의 PR 조회/오류 처리·활성 상세 동기화를 PR binding으로 옮겼다. 공통 controller의 명시적 any 허용도 삭제했다.
Phase 4 — 실제 행동과 계약
A15 Chromium 7개는 실제 React lifecycle·owner runtime/store와 실제 Home 화면/탐색 binding을 사용한다. Home 데이터 변경, 현재 owner 명령, 탭 왕복, overlay 해제 후 지연 응답, 반복 mount/unmount, 복원 준비, 계정별 직접 기록, read failure 중 초안 보존을 확인한다. U02 22개와 U03 29개는 기존 편집/저장 fixture의 입력·포커스·원본 id·저장 중 추가 입력 보존을 확인한다. 실제 외부 API 전구간 검증과 구분한다.
주요 결정과 그 근거
feature 계약은 좁은 입력을 선언한다. Home에 남은 gymData는 홈 표시용 5종 필드로 제한했고 다른 feature가 이를 빌려 쓰지 않는다. 기록표·검색·소셜·WorkoutFlowBinding에는 필요한 데이터만 직접 전달한다. 전송/DB/원본 권위와 저장 성공 의미는 바꾸지 않았다.
작업 중 드러난 것
- source 검사 14개가 이동 전 root만 읽어 실패했다. 실제 소유 파일로 재조준하고 기존 단언을 유지했다. 감사 목록에는 manifest에 있는 대상만 선언했다.
- 기존 full Chromium 실행 파일에서 spawn UNKNOWN이 발생해 zoom 4개가 시작 전에 실패했다. 원인은 미확인이다. U03에서 이미 검증한 동일 Chromium 작업용 실행 파일을 기존 U03_CHROMIUM_EXECUTABLE로 지정해 4개 모두 통과했다. 공유 설치나 테스트 의미를 수정하지 않았다.
- 필수 precheck에서 이전 후 소비가 끝난 반환값5개·import4개를 찾아 제거했고, 해결된 desktop 미사용 허용도 삭제했다(7aebc993). 최초 precheck는 갱신 전 원격 참조를 보고 중단했으며, fetch 후 최신 release 00a93ad3를 실제 merge한 최종 커밋으로 다시 실행했다.
- 이중 키 호환은 378곳 그대로이며 이동 6곳의 위치만 갱신했다. A15 이전분 UI bridge 13곳은 A16 대상이다.
- 이번 신규 미분류 65개는 기능/계층 규칙으로 등록했다. 전체 기존 미분류 118개·미등록 cron 1개·durable 장부 행 누락 2개는 다른 작업의 범위이며 이번 완성 증거로 세지 않는다.
5. 적용 결과
| 항목 | 전 → 후 / 실제 결과 |
|---|---|
| 공용 전달값 | 실제 206 → 0 |
| appController 줄 수 | 2,650 → 1,491 |
| mobileApp / desktopApp 줄 수 | 2,213 / 1,193 → 67 / 67 |
| Phase 2·3·4 전체 check | 각각 3,579 pass / 0 fail / 70 조건부 skip |
| 신규 binding 단위 | Desktop 4 / Mobile 3, 모두 통과. React hook harness이며 DOM 검사 아님 |
| 최종 precheck / Merge Check | 2분24초 · 3,579 pass/0 fail/88 조건부 skip · Merge Check 성공 |
| release 통합 | 앱 PR #1550 · 290eadb9, 2026-09-10 20:39:38 KST |
| 실제 browser | A15 7/7 · U02 22/22 · U03 25+4/29 |
| 잘못된 구현 검출 | feature mutation 7건 검출·원복·재통과 |
| 등록·소유 누락 | A15 신규 65 → 0; 기존 전역 누락은 별도 |
| Production / native / 실기기 | 미실행. 공개 응답 HTTP 200과 실제 인증/저장을 구분 |
6. 이번 개선으로 향상된 것
기능 변경과 해제 책임을 같은 모듈에서 확인할 수 있다
Home 데이터와 명령을 바꾸는 실제 예시는 root import 없이 해당 binding과 화면에서 실행된다. 기능의 selector·modal·요청 해제가 한 곳에 모였고, root에는 owner/auth/route·feature 조립이 남았다. 성능 개선 수치를 추정하지 않았다.
남은 것
A16은 UI bridge/mapper의 기존 느슨한 타입·dual shape를, U05는 실제 import graph와 초기 bundle·구독 비용을, R01은 제품 호출자 없는 completedWorkoutCommands.save/delete와 source 단언의 최종 퇴역을 담당한다. 자세한 실제 파일·삭제 조건은 계약 §10 표에 있다. staging/Production 승격·전체 full CI·배포·이슈 종결은 이번 release 통합 완료와 구분한다.