v1-compat-plan.md: 병행 사용 + 경계 리졸브 브리지로 방향 수렴

사용자가 "v1 런타임을 v2 위에 재구현" 대신 "v1을 그대로 병행 실행하고
경계에서만 값을 리졸브해 넘기는 브리지"를 제안 — 검토 결과 기존에 설계된
state:Observer()(무인자="계속 관측" 유틸)와 v1의 공개 프로퍼티 재대입
API만으로 조립 가능함을 확인, DOMless/엔진값 원칙 덕에 구조적 합성도
이미 공짜라 3-2("얇게 안 됨") 문제를 재구현이 아니라 회피로 해결하는
유력 방향으로 수렴. 조사 중 target()/Linker를 "양방향 바인딩"으로
서술한 이전 오류도 정정(실제로는 named child 등록 + 시그널 중계).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-06 20:19:19 +09:00
parent 34a0e1dcce
commit 24c82a299e
Signed by: qwreey
GPG key ID: D28DB79297A214BD
3 changed files with 99 additions and 35 deletions

View file

@ -45,7 +45,7 @@
| `debug-tooling-plan.md` | 실물 Instance→코드 위치 역추적 Studio 플러그인(`quad-debug`) — 채널 실현 가능성(BindableEvent/Function 크로스 컨텍스트)까지 실측 검증 완료, 세부 API 이름·구현만 남음 | 하 — 사용자가 "quad 개발 완료 전엔 착수 못 함"으로 직접 후순위 지정, base 설계 시 훅 확장 지점만 고려 |
| `documentation-plan.md` | UI 네이밍 컨벤션 문서 + Store 부작용을 게임 시스템에서 깔끔하게 쓰는 패턴 문서 — 뼈대만, `debug-tooling-plan.md` 논의에서 파생 | 하 — 착수 시점 미정, 뼈대만 기록해둔 상태 |
| `ui-shorthand-plan.md` | UICorner/UIPadding/UIScale 인라인 편의 키(v1 `Corner`/`PaddingAll`/`Scale` 선례) — 여전히 필요한 기능으로 재확정(RoundSize만 네이티브 UICorner로 대체돼 불필요), 메커니즘(Handler)·패키지 배치(quad-roblox 코어) 확정 | 하 — 결론 남, M10 전후 구현하면 됨 |
| `v1-compat-plan.md` | v1 하위호환(compat) 레이어 타당성 검토 — 표면 문법(이벤트 self 등)은 opt-in 서브패키지로 얇게 가능, 핵심 런타임(`Class.Extend()` 자동-store+자동재렌더)은 컴포넌트 정체성 모델 충돌로 얇게 안 됨. quad2-try의 `quad-compat`은 빈 폴더로 실제 시도된 적 없었음을 확인 | 하 — 방향(부분 compat vs 마이그레이션 가이드) 사용자 결정 대기 |
| `v1-compat-plan.md` | v1 하위호환(compat) 레이어 타당성 검토 — v1 런타임 재구현(문법 흉내)은 컴포넌트 정체성 모델 충돌로 얇게 안 됨, 대신 **v1을 그대로 병행 실행 + 경계에서만 `state:Observer()`+v1 프로퍼티 재대입으로 값 리졸브해 넘기는 브리지**가 유력 방향으로 수렴(사용자 제안). quad2-try의 `quad-compat`은 빈 폴더로 실제 시도된 적 없었음을 확인 | 하 — 브리지 세부 범위(양방향 필요 여부, 패키지 위치) 사용자 결정 대기 |
## 참고

View file

@ -87,12 +87,13 @@
(Handler)·패키지 배치(quad-roblox 코어)는 확정, 남은 건 이름 재검토
(용어 정리 합류)와 `RoundSize`(이미지 라운드) 대체 방식뿐.
- **v1 하위호환(compat) 레이어 방향**`research/v1-compat-plan.md`(신규,
2026-08-06). 타당성 평가 결론: 이벤트 self 관습 등 표면 문법은 opt-in
서브패키지로 얇게 가능, `Class.Extend()` 자동-store+자동재렌더 같은
핵심 런타임은 컴포넌트 정체성 모델(v1 mutate vs v2 선언적 재구성) 자체가
달라 얇게 안 됨. 사용자 결정 필요: 목표가 "일부 문법만 익숙하게" 인지
"v1 코드를 거의 무수정으로 돌리는 것"인지 — 후자면 compat보다 마이그레이션
가이드가 더 현실적이라는 게 문서의 권장.
2026-08-06, 같은 날 후속 논의로 수렴). v1 런타임을 v2 위에 재구현하는
건(문법 흉내) 컴포넌트 정체성 모델 충돌로 얇게 안 되지만, **v1을 그대로
두고 v2와 병행 실행 + 경계에서만 `state:Observer()`(lazy 포기, 항상
관측)로 값을 리졸브해 v1 프로퍼티에 재대입하는 브리지**는 유력 방향으로
수렴(사용자 제안, 기존 base 원시들만으로 조립 가능함을 확인). 남은 결정:
이 브리지가 양방향(v1→v2도) 필요한지, 브리지 글루 코드를 별도 패키지로
뺄지.
## 참고: 지금까지 확정된 것 (요약)

View file

@ -31,7 +31,14 @@ v1(`.claude/initreq/quad/src`) 조사 결과, API는 성격이 다른 두 계층
- 이벤트 핸들러가 첫 인자로 `self`(or `this`)를 받는 관습(`event.lua:81-83`)
- 프로퍼티 테이블의 특수 키(`RoundSize`/`Corner`/`PaddingAll`/`Scale`,
`class.lua:134-213`)
- `target()`을 통한 양방향 프로퍼티 바인딩(Linker, `class.lua:352-358`)
- `target()`(정확히는 컴포넌트 내부 `self("이름")` 호출)을 통한 named
child 등록 + 시그널 중계(Linker, `class.lua:112-131,352-358,511-521`) —
**정정(2026-08-06)**: 최초 조사 때 "양방향 바인딩"으로 잘못 서술했음.
실제로는 데이터 동기화가 아니라, Linker 값을 숫자 키(자식 위치)에 놓으면
생성된 자식을 `target[name]`에 한 번 등록(`Link`의 `indexType=="number"`
분기, `rawset`)하고, 문자열 키(이벤트 값)에 놓으면 자식 이벤트 발생마다
`target:GetPropertyChangedSignal(name)`을 대신 Fire하는 시그널 중계일
뿐 — "이름 있는 자식 참조 등록"에 더 가까움.
- `store.GetObjects("a,b&c")` 쿼리 문법의 오브젝트 태그 저장소(`store.lua:103-190`)
**(b) 핵심 런타임** — v1 컴포넌트 모델 그 자체:
@ -57,9 +64,10 @@ v1(`.claude/initreq/quad/src`) 조사 결과, API는 성격이 다른 두 계층
`research/ui-shorthand-plan.md`에서 네이티브 포팅 확정됨 — 별도 compat
작업 불필요. `RoundSize`(이미지 9-slice 라운드 트릭)만 UICorner 없던
시절 워크어라운드라 재현 자체가 불필요하다고 이미 결론남.
- **`target()` 양방향 바인딩**: v2 State가 이미 get/set 가능한 핸들이라
"두 State를 서로 미러링하는 어댑터 함수" 정도는 가능해 보임 — 정확한
구현은 미검증, 착수 시점에 확인 필요.
- **`target()`/Linker**: 위 정정대로 "이름 있는 자식 등록 + 시그널 중계"일
뿐이라, v2 쪽에서 굳이 흉내낼 이유가 약함 — v2엔 이미 Ref가 있고(컴포넌트
경계로 참조를 넘기는 표준 경로), 시그널 중계는 아래 4번 브리지 메커니즘이
흡수함.
- **오브젝트 태그 조회**: v2엔 대응 개념이 아예 없음 — `CollectionService`
태그로 유사 구현은 가능하나 새 서브시스템에 가까워 "얇다"고 하기 애매.
@ -84,36 +92,91 @@ v1(`.claude/initreq/quad/src`) 조사 결과, API는 성격이 다른 두 계층
다수의 호환 플래그 + 성능 오버헤드 경고가 딸린 규모라 "얇다"고 부르기
어려움.
## 4. 결론 / 권장
## 4. 사용자 제안 — v1/v2 병행 사용 + 경계 리졸브 브리지 (2026-08-06 후속, 유력 방향)
- **"v1 코드를 무수정으로 돌리는 완전 하위호환"은 사실상 불가능하거나,
적어도 "얇은 래퍼"라는 표현에 맞지 않음** — 근본 원인은 API 이름
불일치가 아니라 컴포넌트 정체성 모델(OOP mutate vs 선언적 재구성) 충돌.
- **문법 설탕 수준의 부분 compat은 시도할 가치 있음** — 이벤트 self 관습,
양방향 바인딩 헬퍼 정도는 opt-in 서브패키지로 저비용 제공 가능. 단 이건
"v1 코드가 그대로 돈다"가 아니라 "v1에서 넘어온 사람이 익숙한 문법
일부를 v2 컴포넌트 함수 안에서 부분적으로 쓸 수 있다"는 수준.
- **"v1 코드베이스 자체를 옮기는 것"이 목표라면 compat보다 마이그레이션
(자동 변환 스크립트 또는 수동 포팅 가이드)이 더 현실적** — `question.md`
이미 있는 "v1 `objectListClass.__newIndex` 오타 기능 재현 테스트, 마이그레이션
가이드 작성 시점에 필요" 항목도 이 방향을 전제하고 있어 자연스럽게 합류
가능.
사용자가 3-2의 "얇게 안 됨" 결론에 대한 대안으로 제시한 방향: v1 런타임을
v2 위에 재현하려 하지 말고, **v1을 그대로, 수정 없이 계속 돌리면서** v2와
병행 사용하고, 두 시스템의 경계(v2가 만든 반응형 값을 v1 쪽에 넘겨야 하는
지점)에서만 작은 브리지를 둔다는 아이디어. 검토 결과 **이쪽이 3-1/3-2보다
분명히 나은 방향** — 아래 근거.
### 왜 이게 작동하는가
1. **구조적 합성은 이미 공짜**`architecture.md:11,18`의 DOMless 원칙상
v1/v2 둘 다 렌더 결과가 그냥 평범한 Roblox Instance라, v1이 만든
Instance를 v2 트리 안에 자식으로 두거나 그 반대나 특별한 어댑터 없이
Roblox 부모-자식 관계만으로 합성됨. 3-2가 문제 삼은 "컴포넌트 정체성
충돌"은 **v1 컴포넌트 자체를 v2로 재구성하려 할 때만** 발생하는 문제고,
"v1 컴포넌트를 그대로 두고 옆에 놓기"에는 애초에 적용되지 않음.
2. **v2→v1 값 전달(사용자가 든 예시)도 이미 있는 재료로 충분히 얇음**:
- v2 쪽: `state:Observer()`를 인자 없이 호출하면 "이 State를 계속
능동 관측 상태로 유지"하는 유틸로 동작(`base/bind-system-plan.md:441`)
— 이걸로 lazy를 포기하고 항상 최신값이 계산되게 강제하는 부분이 이미
설계돼 있음. 사용자가 말한 "포기하고 전부 관측된 값으로" 정확히 이 API.
- v1 쪽: 만들어진 v1 인스턴스에 `instance.Text = value`처럼 그냥
재대입하면 v1의 진짜 공개 API(`class.lua:543-566`의 `__newindex`)를
타고 v1 자신의 업데이트 파이프라인(`UpdateTriggers`, 재렌더)이 정상
작동함 — v1 내부를 뜯어 흉내낼 필요 없이 v1이 원래 하던 일을 밖에서
호출만 하는 것.
- 합치면: `state:Observer(function() v1Instance.Text = state:Get() end)`
한 줄 수준의 브리지로 "v2 State가 바뀔 때마다 v1 인스턴스 프로퍼티에
써주기"가 됨 — 3-2에서 우려한 "v1 런타임 재구현"이 전혀 필요 없음.
3. **정반대 방향(v1→v2)도 필요하다면 대칭적으로 얇음(미검증, 방향성만)**:
v1은 `GetPropertyChangedSignal`/`EmitPropertyChangedSignal`
(`class.lua:407-437`)을 이미 공개 API로 노출하므로, 그 시그널을 구독해서
매번 v2 `Source:Set()`(또는 clone 불가 값이면 `:Emit()`)을 호출해주는
것도 같은 패턴 — 다만 사용자가 예시로 든 건 v2→v1 한 방향뿐이라, 실제로
양방향이 필요한지는 아래 열린 질문으로 남김.
4. **경계 코드의 라이프사이클 정리도 새로 설계할 필요 없음** — 브리지용
Observer 구독을 v1 인스턴스(진짜 Roblox Instance)의 `Destroying`
묶으면 됨, 이미 채택된 rbvm `Connected`+GC 관용구(`base/
lifecycle-pattern.md`)를 그대로 재사용.
### 3-1(문법 설탕 compat)과의 관계
이 방향은 3-1의 "이벤트 self 관습, 프로퍼티 특수 키" 같은 **v1 쪽 표현을
v2 문법으로 흉내내는 작업 자체를 없앰** — v1 코드는 그냥 v1 문법 그대로
남아있고, v2는 v1을 흉내낼 필요가 없음. 즉 "compat 레이어가 v1처럼 보이게
만드는" 문제가 "v1이 원래 하던 일을 그대로 하게 두고 데이터만 새 파이프로
갈아끼우는" 훨씬 좁은 문제로 축소됨.
## 5. 결론 / 권장
- **1순위(신규 권장)**: 4번의 "병행 사용 + 경계 리졸브 브리지" — v1을
그대로 두고 v2와 나란히 돌리되, 반응형 값이 경계를 넘는 지점만 각 쪽의
기존 공개 API(v2 `state:Observer()`, v1 프로퍼티 재대입/시그널)로 잇는
얇은 글루 코드. 3-2가 지적한 "컴포넌트 정체성 모델 충돌"을 재구현이
아니라 회피로 해결 — 사실상 strangler-fig식 점진 마이그레이션 패턴.
- **2순위(보조)**: 3-1의 문법 설탕 어댑터(이벤트 self 등) — 위 1순위로
충분하다면 불필요할 수 있음, "v1 문법 자체를 v2 컴포넌트 함수 안에서
쓰고 싶다"는 별도 니즈가 있을 때만 검토.
- **"v1 코드를 완전히 무수정으로 v2 런타임 위에서 돌리는 것"(3-2, OOP
mutate 재구현)은 여전히 비권장** — 1순위 방향이 그 문제 자체를 안 만들기
때문에 불필요.
- 확정된 소스 트리(`base/architecture.md` "구현 착수" 절)는 `quad-base`/
`quad-roblox` 두 패키지뿐 — `quad-compat`은 그 목록에 없음. 부분
compat을 실제로 만든다면 세 번째 패키지를 새로 추가하는 결정이 필요함.
`quad-roblox` 두 패키지뿐 — 경계 브리지 글루 코드를 어디 둘지(별도
`quad-compat` 패키지 신설 vs 필요한 프로젝트마다 로컬 유틸)는 아래 열린
질문.
## 5. 열린 질문 (사용자 판단 필요)
## 6. 열린 질문 (사용자 판단 필요)
- 목표가 "v1 문법 일부를 v2에서도 익숙하게 쓰게" 인지, "v1 코드를 무수정
또는 최소 수정으로 돌리는 것"인지 — 답에 따라 부분 compat vs 마이그레이션
가이드 중 방향이 갈림.
- 부분 compat으로 간다면 `quad-compat`을 정식 세 번째 서브패키지로 추가할지.
- 이벤트 self 관습을 compat에서 되살릴 때, `base/bind-system-plan.md`
이미 명시한 반대 근거 4번(quad-debug 추적 밖 mutate 경로)을 어떻게
처리할지(quad-debug는 어차피 후순위라 지금 결정 불필요할 수도 있음).
- **양방향이 실제로 필요한가, 한쪽 방향(v2→v1, 데이터 새로 짜고 v1 UI는
유지)만으로 충분한가** — 사용자 예시는 v2→v1 한 방향. 반대 방향까지
필요하면 4번의 3번 항목(v1 시그널 구독 → v2 Source 갱신)을 실제로
설계해야 함.
- 브리지 글루 코드를 별도 패키지(`quad-compat`, 소스 트리에 아직 없음)로
뺄지, 아니면 정식 패키지 없이 "필요할 때 짜는 유틸 패턴" 정도로 문서화만
해둘지.
- v1 store의 `registerClass` 체이닝 기능(`:Tween`/`:Default` 등)까지
브리지가 흡수해야 하는 케이스가 있는지 — 단순 프로퍼티 재대입만으로
충분한 범위인지 실사용 예시로 확인 필요.
- (3-1을 실제로 병행 채택할 경우) 이벤트 self 관습을 compat에서 되살릴 때,
`base/bind-system-plan.md`가 명시한 반대 근거 4번(quad-debug 추적 밖
mutate 경로)을 어떻게 처리할지 — quad-debug는 어차피 후순위라 지금 결정
불필요할 수도 있음.
## 착수 시점
지금 당장 설계/착수 불필요 — `CLAUDE.md` "지금 할 일" 1번(구현 착수,
ROADMAP M0)이 최우선. 이 문서는 타당성 평가 결과만 남겨두고, 실제 설계는
사용자가 방향(부분 compat vs 마이그레이션 가이드)을 정한 뒤 진행.
사용자가 방향(4번 병행+브리지의 세부 범위)을 정한 뒤 진행.