로드맵 인수인계 라운드(2026-08-04) 결과 반영 — ROADMAP.md 신설, 설계 단계 종료
component-composition-plan을 research/에서 base/로 승격하고, 구현 착수 전 리스크 감사에서 나온 M0 스파이크 항목(Store/State propagation, dispatch, 컴포넌트 경계 named-parameter 전달)을 문서에 반영. quad-base 테스트 mock 방향(Vide 선례 채택) 확정.
This commit is contained in:
parent
c19e82f661
commit
470f188d0f
9 changed files with 532 additions and 167 deletions
|
|
@ -157,6 +157,36 @@ quad/
|
||||||
Tween/purity/existing-instance-bind는 여전히 `research/`에 남아있고 이
|
Tween/purity/existing-instance-bind는 여전히 `research/`에 남아있고 이
|
||||||
구조 확정을 막지 않음.
|
구조 확정을 막지 않음.
|
||||||
|
|
||||||
|
## 테스트 전략: quad-base용 최소 mock (2026-08-04)
|
||||||
|
|
||||||
|
**결정**: quad-base 테스트는 Vide 선례(`initreq/vide/test/mock.luau`, 약
|
||||||
|
300줄)를 따라 최소한의 mock으로 감 — parent/children 트리 + 타입 검증 없는
|
||||||
|
property bag + property별 변경 시그널 정도만 흉내내고, `IsA()`/클래스별
|
||||||
|
프로퍼티 스키마/`WaitForChild`/`DataModel` 같은 건 안 만듦. 순수 `luau` CLI로
|
||||||
|
Studio/엔진 없이 테스트(Vide가 실제로 이렇게 CI에 물려놓음) — Fusion처럼
|
||||||
|
Studio 안에서만 도는 방식은 채택 안 함. 근거: quad-base 코어(Store/State/
|
||||||
|
Source/Modifier/Slot, 디스패치 엔진)는 이미 `inst`를 `any`로 취급하고
|
||||||
|
Instance 특정 동작을 전혀 참조하지 않도록 설계돼 있어(`bind-system-plan.md`
|
||||||
|
"inst가 항상 Roblox Instance일 필요는 없음" 절), mock이 실제 Roblox 충실도를
|
||||||
|
가질 이유가 없음.
|
||||||
|
|
||||||
|
**스코프는 "정적 디버깅"으로 한정** — **사용자 확정**: mock으로 확인하려는
|
||||||
|
건 한 시점의 렌더 결과(정적 스냅샷)지, 시간에 따라 변하는 동적 동작(Tween
|
||||||
|
애니메이션, 타이밍 등)이 아님. 그래서 지금 단계 mock엔 시간 기반 핸들러를
|
||||||
|
흉내낼 계획이 없음.
|
||||||
|
|
||||||
|
**"quad-roblox로 작성한 컴포넌트가 mock에서도 그대로 돌아가야 한다"는 요구는
|
||||||
|
없음** — **사용자 확정**("이건 꼭 지켜질 필요까지 있진 않아, 단순하게 가도
|
||||||
|
됨"). mock은 quad-roblox의 실제 핸들러(ReflectionService 기반 이벤트 판별,
|
||||||
|
CollectionService 태그 등)를 흉내낼 필요가 없고, quad-base 자체 로직(디스패치
|
||||||
|
엔진, Store/State/Source, Modifier, Slot)만 검증하면 충분 — quad-roblox
|
||||||
|
개발과는 무관해도 됨.
|
||||||
|
|
||||||
|
**백로그**: 나중에 범용 렌더 결과 디버깅 도구로 키우고 싶어지면(정적
|
||||||
|
스냅샷을 넘어 Tween mock 같은 동적 동작까지 포함) 그때 스코프를 넓히는
|
||||||
|
걸로 — 지금은 quad-base 테스트 전용 최소 mock까지만(`CLAUDE.md` 백로그
|
||||||
|
참고).
|
||||||
|
|
||||||
## Store/State/Source 온톨로지 — 확정됨 (요약)
|
## Store/State/Source 온톨로지 — 확정됨 (요약)
|
||||||
|
|
||||||
Store는 source(실제 값이 존재하는 단일 지점) 집합체이고, `store.key`로 접근할
|
Store는 source(실제 값이 존재하는 단일 지점) 집합체이고, `store.key`로 접근할
|
||||||
|
|
@ -171,8 +201,9 @@ Store와 별개인 가벼운 `Source` 프리미티브를 씀. `store.key` dot-ac
|
||||||
|
|
||||||
## 아직 미정 (research/로 분리됨)
|
## 아직 미정 (research/로 분리됨)
|
||||||
|
|
||||||
Tween 플러깅, 이미 생성된 인스턴스에 대한 바인드, 컴포넌트가 modifier/Ref를
|
Tween 플러깅, 이미 생성된 인스턴스에 대한 바인드 — `.claude/research/` 각
|
||||||
경계 너머로 어떻게 전달하는지 — `.claude/research/` 각 문서 참고, 전체 색인은
|
문서 참고, 전체 색인은 `.claude/README.md`. 바인드 디스패치/Slot/모듈
|
||||||
`.claude/README.md`. 바인드 디스패치/Slot/모듈 라이프사이클은 위 "구현 착수"
|
라이프사이클/Modifier/컴포넌트화(컴포넌트 경계 modifier/Ref 전달 포함)는
|
||||||
섹션대로 확정되어 `.claude/base/`로 승격됨(`bind-system-plan.md`/
|
위 "구현 착수" 섹션대로 확정되어 `.claude/base/`로 승격됨
|
||||||
`module-lifecycle-plan.md`/`slot-plan.md`).
|
(`bind-system-plan.md`/`module-lifecycle-plan.md`/`slot-plan.md`/
|
||||||
|
`modifier-plan.md`/`component-composition-plan.md`).
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,16 @@ src/schema/union.luau:48-68`) — 에러 메시지는 즉시 문자열로 만들
|
||||||
- **다른 값 변경을 추적하는 것도 process 함수의 정상 범위**: 예를 들어 Slot
|
- **다른 값 변경을 추적하는 것도 process 함수의 정상 범위**: 예를 들어 Slot
|
||||||
핸들러는 자기가 감시하는 값(배열/스토어)이 바뀌면 그에 따라 child를
|
핸들러는 자기가 감시하는 값(배열/스토어)이 바뀌면 그에 따라 child를
|
||||||
갱신해야 함 — `retract` 시점엔 그 추적(구독)만 풀면 됨.
|
갱신해야 함 — `retract` 시점엔 그 추적(구독)만 풀면 됨.
|
||||||
|
- **일반적인 무한루프 방어(사이클 감지 등)는 하지 않기로 확정(2026-08-04,
|
||||||
|
로드맵 인수인계 라운드)**: 우선순위 스캔+재귀 `process` 구조 자체는 핸들러가
|
||||||
|
규율을 안 지키면(예: 값을 좁히지/변형하지 않고 같은 값을 그대로 다시
|
||||||
|
`process`에 넘김) 무한루프에 빠질 수 있음 — 하지만 이건 base가 방어 로직을
|
||||||
|
둬야 할 문제가 아니라 오작동하는 handler/provider(`quad-roblox` 등) 쪽
|
||||||
|
버그로 간주 — **사용자 확정**("입력된 값이 다시 입력되면 무한루프
|
||||||
|
빠지겠지만, 그건 막기 힘들고 유저가 내기도 힘들어. 아예 quad-roblox나
|
||||||
|
프로바이더가 잘못 짠 코드일테니까"). Tween의 store-bind 재귀 케이스(위
|
||||||
|
78-79행)처럼 자연히 좁혀지는 경우가 일반적이고, 일반 사용자가 만들어낼 수
|
||||||
|
있는 상황이 아니라고 판단해 별도 가드 없이 진행.
|
||||||
|
|
||||||
## Store 바인드는 특수 경우인가, 아니면 pluggable 바인드를 재실행하는 래핑인가
|
## Store 바인드는 특수 경우인가, 아니면 pluggable 바인드를 재실행하는 래핑인가
|
||||||
|
|
||||||
|
|
|
||||||
259
.claude/base/component-composition-plan.md
Normal file
259
.claude/base/component-composition-plan.md
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
# 컴포넌트화 (Roblox 기본 오브젝트 이외의 사용자 정의 컴포넌트)
|
||||||
|
|
||||||
|
**상태**: base — 2026-08-04 세션(6차 라운드 이후) 채팅 논의로 핵심 골격 +
|
||||||
|
modifier/Ref 컴포넌트 경계 통과 문제까지 전부 확정. 사용자가 "지금 quad에서
|
||||||
|
가장 문제되는 부분"으로 직접 지목했던 주제였으나 이번 라운드에서 수렴 완료.
|
||||||
|
남은 건 API 이름뿐(아래 "남은 열린 질문" 참고). `base/bind-system-plan.md`의
|
||||||
|
Store/State/Source 온톨로지가 먼저 확정된 뒤에야 이 논의가 열림 — 그
|
||||||
|
문서가 선행 컨텍스트.
|
||||||
|
|
||||||
|
## 문제
|
||||||
|
|
||||||
|
v1의 `Class.Extend()`(Init/Render/AfterRender/Getter/Setter/UpdateTriggers,
|
||||||
|
`base/quad-v1-architecture.md` 참고)는 이미 OOP 상속 스타일이라 폐기
|
||||||
|
방향이지만, 그게 제공하던 실제 편의 기능(컴포넌트가 자기 store를 자동으로
|
||||||
|
가짐, props로 넘어온 State를 자동 흡수, `self:Default`/`self "key"`로
|
||||||
|
기본값·바인딩)까지 같이 버려도 되는지가 미결이었음. `MyComp {...}` 형태로
|
||||||
|
호출되는 사용자 정의 컴포넌트를 v2에서 어떤 모양으로 작성하게 할지가 핵심
|
||||||
|
질문.
|
||||||
|
|
||||||
|
## v1 실제 메커니즘 (조사 완료, `quad.qwreey.kr` 튜토리얼 + `initreq/quad/src/` 소스로 교차검증)
|
||||||
|
|
||||||
|
- `myStore "key"` → register(현재 State에 해당) 반환. `:Default(v)`/
|
||||||
|
`:With(fn)`/`:Add(v)`/`:Tween(opts)` 체이닝 가능(`store.lua:433-457`).
|
||||||
|
- `Class.Extend()`의 `:Init(props)`에서 `props:Default("Size", v)`로 기본값
|
||||||
|
설정, props 테이블 자체가 store 인스턴스로 변신(`class.lua:365-379`,
|
||||||
|
`storeNew(prop,nil)`).
|
||||||
|
- props로 넘어온 값이 State(`quad_register`)면 `initStoreRegisterBinding`
|
||||||
|
(`store.lua:394-431`)이 자동으로 감지해 컴포넌트 자신의 store 키에 재귀
|
||||||
|
연결 — **자동 흡수 매직**이 실제로 존재했음.
|
||||||
|
- `self(name)` linker가 **두 가지 역할**을 겸함: (1) `self "_button"`을
|
||||||
|
자식 자리에 넣으면 렌더링된 인스턴스를 `self._button`에 즉시 잡아둠(Ref
|
||||||
|
역할) (2) `[Event.Prop "Text"] = self "Text"`로 인스턴스 프로퍼티 변경을
|
||||||
|
다시 컴포넌트 store로 역방향 전파(양방향 바인딩, `EmitPropertyChangedSignal`
|
||||||
|
자동 연결과 동일) — quad.qwreey.kr 튜토리얼 `11_extend/` 문서 원문 확인.
|
||||||
|
|
||||||
|
이 두 역할이 v2 온톨로지에서는 이미 갈라져 있음: (1)은 확정된 **Ref**가
|
||||||
|
대체, (2)는 이번 논의에서 다루는 Source 양방향 프록시가 대체.
|
||||||
|
|
||||||
|
## 수렴된 결론
|
||||||
|
|
||||||
|
### 1. 컴포넌트 = 그냥 함수, "자기 store 자동 소유" 매직은 폐기
|
||||||
|
|
||||||
|
`MyComp = function(props) return Frame {...} end`, 호출 규약은
|
||||||
|
`Frame{...}`와 동일(`MyComp{...}` → `MyComp(propsTable)`). v1의 Extend
|
||||||
|
자동-store-생성+자동-흡수 매직은 재현하지 않음 — 대신 React식으로 호출부가
|
||||||
|
State/raw/Source/콜백 중 뭘 넘길지 명시적으로 고름. 이유: 자동 흡수는
|
||||||
|
매 컴포넌트 호출마다 "이 prop이 State인가?" 타입 분기를 프레임워크가
|
||||||
|
암묵적으로 수행해야 하는 매직이고, 명시적 전달이 더 단순·예측 가능(React가
|
||||||
|
Vue/Svelte 대비 내세우는 강점과 동일 논리) — **사용자 확정**("마법 안쓴다
|
||||||
|
그것도 동의함").
|
||||||
|
|
||||||
|
### 2. State/Source 경계 규칙: 파생이면 읽기전용, 원본이면 쓰기 가능
|
||||||
|
|
||||||
|
State는 `:With`/`:Compute`로 만들어진 파생값일 수 있어 쓰기가 정의 자체가
|
||||||
|
안 됨. Source(독립이든 Store 소속 `StoreSource` 프록시든)는 파생이 아니라
|
||||||
|
항상 원본 슬롯 하나를 직접 가리키므로 쓰기가 의미 있음 — **사용자 확정**
|
||||||
|
("맞음. 확실해").
|
||||||
|
|
||||||
|
### 3. `StoreSource`: Source를 인터페이스+구현체로 두고, Store 키에서 그 인터페이스를 구현하는 얇은 프록시를 받음
|
||||||
|
|
||||||
|
- **Source = 인터페이스이자 구현체**: 독립 생성자 `Source(initial)`가 기본
|
||||||
|
구현체, `store:GetSource("key")`(가칭)류 접근자가 반환하는 값은 같은
|
||||||
|
인터페이스를 구현하는 별도의 얇은 프록시(`StoreSource`) — 읽기는
|
||||||
|
`store.key`로, 쓰기는 `store.key = v`로 위임. **내부 Source 객체를 그대로
|
||||||
|
노출하지 않음** — 그러면 "쓰기는 오직 Store의 `__newindex`뿐"이라는 기존
|
||||||
|
확정과 새 쓰기 경로가 충돌하게 됨.
|
||||||
|
- **캐시 안 함**: State가 이미 "매번 새로 만듦, store에 캐시 안 됨"으로
|
||||||
|
확정돼 있어 일관성 + 엔지니어링 비용 둘 다 이쪽이 쌈 — **사용자 확정**
|
||||||
|
("그냥 엔지니어링적으로 비용이 싼거 택해").
|
||||||
|
|
||||||
|
### 4. Source 직접 전달(양방향)은 핸들러 계약 확장 없이 타입 유니온으로 처리 — 단, 실사용 범위는 좁음
|
||||||
|
|
||||||
|
- 핸들러가 값을 받을 때 `Source<T> | State<T>` 유니온으로 받고, 내부에서
|
||||||
|
타입 체크만 하면 됨(Source면 인스턴스 변경 이벤트에 걸어 역방향 쓰기까지
|
||||||
|
처리, State면 읽기만) — `isHandlable`/`priority`/`process`/`retract` 4종
|
||||||
|
계약에 5번째 항목을 추가할 필요 없음. Source 자체가 계산이 없는 원천이라
|
||||||
|
가능한 단순화 — **사용자 확정**("그냥 타입 상 source를 받거나 state를
|
||||||
|
받거나 하면 됨. source 자체는 원천이라 컴퓨팅 같은거 없어").
|
||||||
|
- **하지만 실사용은 좁을 것으로 예상**: `isEnabled`처럼 여러 조건에 영향
|
||||||
|
받는(=파생된) 값은 애초에 State지 Source가 아니므로 이 경로로 못 넘김.
|
||||||
|
즉 Source 직접 전달이 통하는 건 진짜 단순한 1:1 원본-토글 케이스뿐이고,
|
||||||
|
일반적인 경우엔 React식 `value(State) + onChange(callback)` 패턴이 기본
|
||||||
|
— **사용자 확정**("isenabled가 여러 조건에 영향 받으면 바로 문제가
|
||||||
|
생기는거지. 따라서 실제 사용은 제한적일듯. callback을 쓰는게
|
||||||
|
일반적이여 보이긴 해. 타입으로도 편하기도 하고 디버깅도 편함").
|
||||||
|
|
||||||
|
### 5. 리프(Roblox 프로퍼티) 바인딩엔 원칙적으로 State만
|
||||||
|
|
||||||
|
계산된 최종값만 실제로 인스턴스에 반영되어야 하므로, 리프 바인딩은 State가
|
||||||
|
일반 경로. Source는 리프 바인딩용 프리미티브가 아니라, 아주 단순한 구조에서
|
||||||
|
콜백 보일러플레이트를 줄이기 위한 좁은 용도의 예외 — **사용자 확정**
|
||||||
|
("리프 바인딩엔 state만 쓰이지 않을까... source는 그냥 아주 단순한
|
||||||
|
구조에서 콜백을 넣고 하는 복잡함을 줄이기 위함일 뿐임").
|
||||||
|
|
||||||
|
## 프레임워크 사례 조사 (2026-08-04, modifier/Ref 경계 통과 문제 관련)
|
||||||
|
|
||||||
|
병렬 리서치로 4개 소스(Compose 공식 문서, Fusion/Vide 소스, quad v1 +
|
||||||
|
PA artworks)를 확인. **결론: 조사한 어떤 선례도 "컴포넌트 경계에서 modifier/Ref
|
||||||
|
전달" 문제를 완전히 풀어놓지 않음** — 심지어 quad가 이미 많이 참고한 Fusion도
|
||||||
|
multi-root를 지원은 하지만 그 상태에서 외부 ref/props를 특정 root에 연결하는
|
||||||
|
관례는 자체 문서에도 없음.
|
||||||
|
|
||||||
|
### Compose의 Modifier는 애초에 flat property bag이 아님 — 순서 의존적 wrapper 체인
|
||||||
|
|
||||||
|
`modifier-plan.md`가 이미 확정한 "필드 단위 flatten, 나중 게 이김" 모델과
|
||||||
|
Compose의 실제 메커니즘은 근본적으로 다른 종류임. Compose `Modifier`는
|
||||||
|
`CombinedModifier`(2-노드 연결 리스트)로 순서대로 이어붙는 wrapper 체인 —
|
||||||
|
`Modifier.padding(16.dp).clickable(onClick)` vs
|
||||||
|
`Modifier.clickable(onClick).padding(16.dp)`가 실제로 다르게 동작함(패딩
|
||||||
|
영역이 클릭 가능한지 여부가 순서에 따라 갈림, 공식 문서 예시).
|
||||||
|
`.then()`/`+`는 "같은 프로퍼티면 덮어쓰기"가 아니라 순수 **연결(concatenation)**.
|
||||||
|
→ **quad의 "필드명 기준 last-wins" 모델은 Compose를 그대로 벤치마킹한 게
|
||||||
|
아니라 독자 설계임을 확인** — Compose와의 유사성은 "관례로 경계를 넘긴다"는
|
||||||
|
아이디어 수준에서만 성립, merge 의미론까지 가져올 근거는 아님.
|
||||||
|
|
||||||
|
공식 API 가이드라인(`compose-api-guidelines.md`,
|
||||||
|
`compose-component-api-guidelines.md`, `mrmans0n/compose-rules` 린트)이
|
||||||
|
명시하는 규칙:
|
||||||
|
- `modifier` 파라미터는 이름 고정, 타입 `Modifier`, 기본값 `Modifier`, 첫 번째
|
||||||
|
optional 파라미터여야 함.
|
||||||
|
- 받은 modifier는 컴포저블이 만드는 루트 레이아웃 노드에 **체인의 맨 앞**에
|
||||||
|
적용, 필요하면 뒤쪽에 이어붙이는 것만 허용(앞에 붙이는 것 금지).
|
||||||
|
- 같은 modifier 인스턴스를 여러 노드에 나눠 쓰지 말 것(단일 소비 전제).
|
||||||
|
|
||||||
|
**Multi-root(루트가 여럿인 컴포저블)에 대한 공식 답은 없음** — 오히려
|
||||||
|
가이드라인은 `CheckboxRow(rowModifier, checkboxModifier)`처럼 파트별
|
||||||
|
modifier 파라미터를 두는 패턴을 명시적으로 **반례(DON'T)**로 제시하며
|
||||||
|
"modifier는 컴포넌트 자체의 외부 동작을 위한 것이지 하위 파츠용이 아님,
|
||||||
|
대신 슬롯(자식 컴포저블 람다)으로 만들어라"라고 함. 즉 Compose는 이 문제를
|
||||||
|
**풀지 않고 애초에 안 생기게 architecture로 피함**(multi-root 자체를 권장하지
|
||||||
|
않고 slot 패턴으로 유도).
|
||||||
|
|
||||||
|
### Fusion — modifier 개념 자체가 없음, multi-root는 있지만 ref 전달 관례 없음
|
||||||
|
|
||||||
|
- `merge.luau`(`src/Utility/merge.luau:13-33`)는 scope 메소드 테이블 병합용이지
|
||||||
|
props 병합용이 아님 — quad Modifier에 대응하는 게 Fusion엔 없음.
|
||||||
|
- prop 전달 관례는 전부 **named table**(`props.Layout.Size`,
|
||||||
|
`props[Children]`) — 배열 아이템으로 뭔가를 넘기는 관례 자체가 없음.
|
||||||
|
Children도 예약된 `[Children]` 키로 감, 포지셔널 아님.
|
||||||
|
- `New()`/`Hydrate()`는 raw Instance 리턴(quad와 동일 지점).
|
||||||
|
- **Multi-root 컴포넌트는 실제로 지원**(`docs/tutorials/best-practices/
|
||||||
|
instance-handling.md:17-61` — "Instance 배열 리턴, 여러 값 리턴 대신 배열로
|
||||||
|
감싸라"), 하지만 **외부에서 넘어온 ref/props를 그중 특정 root에 연결하는
|
||||||
|
예시나 관례는 문서에 없음** — quad가 지금 맞닥뜨린 것과 완전히 같은 질문이
|
||||||
|
Fusion 자체 문서에서도 답이 안 나가 있음.
|
||||||
|
|
||||||
|
### Vide — modifier도, 배열 기반 전달 관례도, multi-root 사례도 전무
|
||||||
|
|
||||||
|
`src/`/`docs/` 전체에 `modifier`/`merge`/`combine`/`spread` 매칭 0건. 모든
|
||||||
|
컴포넌트 예제가 named+typed `props` 테이블을 필드별로 직접 옮겨씀. 인스턴스
|
||||||
|
생성자는 raw Instance 리턴. multi-root 예제/개념 자체가 문서에 존재하지 않음.
|
||||||
|
|
||||||
|
### quad v1 — 배열 아이템 구분은 항상 런타임 `__type` 태그로 함
|
||||||
|
|
||||||
|
`ProcessQuadProperty`(`class.lua:134-213`)는 배열 위치의 모든 아이템을
|
||||||
|
`__type`으로 검사(`quad_linker`/`quad_register`/`quad_style` 중 하나면 그
|
||||||
|
용도로, 아니면 무조건 자식으로 마운트) — v2의 "리프 레벨에서 타입으로
|
||||||
|
Modifier/Ref/자식을 구분"이 이미 v1의 유일한 해법이었던 패턴 그대로임을
|
||||||
|
확인. 단 v1도 multi-root 사례가 전혀 없어서, "컴포넌트가 여러 루트를 반환할
|
||||||
|
때"는 v1도 답을 준 적이 없음. PA artworks에도 컴포넌트 추상화/multi-root
|
||||||
|
사례 없음(재사용 가능한 컴포넌트 함수 자체가 아직 코드로 존재하지 않음).
|
||||||
|
|
||||||
|
### 종합
|
||||||
|
|
||||||
|
| | Modifier-equiv 있음? | 전달 관례 | multi-root 지원 | multi-root 시 ref/modifier 전달 관례 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Compose | O(순서의존 체인) | named 파라미터 강제(린트) | 사실상 비권장, slot으로 유도 | 없음(애초에 안 만듦) |
|
||||||
|
| Fusion | X | named table | O(배열 리턴) | **없음(미해결로 확인)** |
|
||||||
|
| Vide | X | named table | 사례 없음 | 해당 없음 |
|
||||||
|
| quad v1 | X(런타임 태그로 대체) | 태그 기반 배열 아이템 | 없음 | 없음 |
|
||||||
|
|
||||||
|
시사점: (1) "배열 아이템을 타입으로 구분"은 quad v1 고유 패턴이자 quad-v2
|
||||||
|
리프 레벨이 이미 계승한 것 — 그런데 이 문서 위쪽에서 지적했듯 컴포넌트
|
||||||
|
함수 경계에서는 타입 스니핑을 자동으로 해줄 디스패처가 없어서 저작자가 직접
|
||||||
|
루프를 돌려야 함(v1도 이 경계에서 실제로 쓰인 적이 없어 검증 안 된 채로
|
||||||
|
남음). (2) named-key 전달(Fusion/Vide/Compose 공통)이 "함수 호출만으로
|
||||||
|
경계를 넘는" 상황에서 유일하게 실제로 쓰이고 있는 관례. (3) multi-root +
|
||||||
|
외부 ref/modifier 전달은 **조사한 4개 선례 중 어느 것도 실제로 풀어놓지
|
||||||
|
않음** — Compose는 회피, Fusion은 미해결로 방치, Vide/v1은 애초에 안 함.
|
||||||
|
즉 이 지점은 quad가 진짜 새로 설계해야 하는 부분이지, 어딘가에 있는 답을
|
||||||
|
못 찾은 게 아님.
|
||||||
|
|
||||||
|
## 최종 결론: 컴포넌트 경계 modifier/Ref 전달 (2026-08-04, 확정)
|
||||||
|
|
||||||
|
### 1. Named parameter로 경계를 넘김 — 리프 레벨과는 다른 계약
|
||||||
|
|
||||||
|
컴포넌트 함수(`function(props) return Frame{...} end`)는 `Frame{...}`처럼
|
||||||
|
배열 아이템 + 런타임 타입 스니핑으로 modifier/Ref를 받지 않음 — 함수 호출로
|
||||||
|
경계를 넘는 순간부터는 자동으로 타입을 스니핑해줄 디스패처가 없기 때문(리프
|
||||||
|
레벨의 `ProcessQuadProperty`류 디스패치는 `Frame{...}` 호출 내부에서만
|
||||||
|
동작하고 컴포넌트 함수 몸통엔 적용되지 않음). 대신 caller는 named key(가칭
|
||||||
|
`props.Modifier`/`props.Ref`)로 넘기고, 컴포넌트 저작자가 자기 코드 안에서
|
||||||
|
명시적으로 원하는 내부 `Frame{...}` 호출의 배열 자리에 다시 꽂아넣음
|
||||||
|
(`return Frame { props.Modifier, props.Ref, ... }`) — **사용자 확정**
|
||||||
|
("결과적으로 함수 구현에선 타입을 멀쩡히 지정하는게 더 중요하니 네임드가
|
||||||
|
맞는듯"). Compose(named `modifier` 파라미터 강제, 린트로 감시)와
|
||||||
|
Fusion/Vide(named prop 전달, `[Children]`류 예약 키)가 서로 다른 이유로 전부
|
||||||
|
같은 결론에 도달한 유일한 실용적 패턴 — quad가 발명한 게 아니라 선례가
|
||||||
|
수렴하는 지점(위 "프레임워크 사례 조사" 절 참고).
|
||||||
|
|
||||||
|
### 2. "다중 루트로 반환" 자체를 컴포넌트 개념에서 제거
|
||||||
|
|
||||||
|
기존에 "컴포넌트가 여러 루트를 반환하면 모호해짐"이라던 프레이밍이 서로
|
||||||
|
다른 두 가지를 하나로 섞은 것이었음이 드러나 재정리:
|
||||||
|
|
||||||
|
- **정적으로 고정된 여러 형제 Instance를 한 함수 호출이 그대로 반환**(React
|
||||||
|
Fragment류) — **불필요로 폐기**. 근거 셋: (1) Luau가 tail position 밖에서
|
||||||
|
다중 리턴을 지원 안 함 — `return a, b`는 `Frame{ MyComp{...}, other }`처럼
|
||||||
|
배열 중간에 놓이는 순간 첫 값만 살아남으므로, 언어 차원에서 이 패턴이
|
||||||
|
애초에 자연스럽게 지원되지 않음(**사용자 확인**). (2) 필요하면 호출부에서
|
||||||
|
그냥 여러 컴포넌트를 나란히 쓰면 됨(`Frame{ IconA{...}, LabelB{...} }`) —
|
||||||
|
한 컴포넌트 호출이 몰래 여러 형제를 뿜어낼 이유가 없음. (3) 프레임워크
|
||||||
|
조사에서도 진짜 수요가 있어 제대로 지원된 사례가 없음(Fusion은 "된다"고만
|
||||||
|
하고 ref 연결 관례는 미해결로 방치, Compose는 아예 안 만들도록 가이드) —
|
||||||
|
어려워서 방치된 게 아니라 실제 수요가 없어서 아무도 안 만든 것 —
|
||||||
|
**사용자 확정**("그럴 필요가 있나 싶네... 애초에 다중 리턴이 될 이유가
|
||||||
|
없는듯").
|
||||||
|
- **컴포넌트가 Slot을 반환**(개수가 가변적인 자식 묶음을 부모의 형제 레벨에
|
||||||
|
래퍼 없이 그대로 펼침 — 예: `ItemList{items=state}`가 `UIListLayout` 밑에서
|
||||||
|
래퍼 Frame 없이 `Header{}`/`Footer{}`와 같은 레벨로 항목들을 끼워넣는 경우)
|
||||||
|
— **이미 있는 별개 메커니즘**(`base/slot-plan.md`), 새 설계 불필요. Slot은
|
||||||
|
단일 Instance 정체성이 없으므로, 이런 컴포넌트는 애초에 `Modifier`/`Ref`
|
||||||
|
파라미터를 선언하지 않으면 그만 — 타입 시그니처 자체가 "나는 단일 대상에게
|
||||||
|
적용할 modifier/Ref가 없다"를 표현. 별도 조율 메커니즘 불필요 — **사용자
|
||||||
|
확정**("불가능하진 않고 기술적으로도 충분히 되는 일... 엄청 집중해야할
|
||||||
|
일은 아니지 않을까").
|
||||||
|
|
||||||
|
이 정리로 원래의 "모호해지는 케이스"는 사라짐: 컴포넌트가 단일 root를 갖는
|
||||||
|
한 named parameter로 명확히 전달되고, 단일 root가 없는 컴포넌트(Slot 반환)는
|
||||||
|
애초에 그 파라미터를 안 받으므로 모호함이 생길 지점 자체가 없음. 반환값에
|
||||||
|
"사후적으로" 뭔가를 꽂아넣는다는 그림 자체가 틀렸던 것 — forwarding은 항상
|
||||||
|
컴포넌트가 반환하기 *전에*, 저작자 코드 안에서 일어나는 일이라 어느 root로
|
||||||
|
가야 하는지는 저작자가 자기 코드에 뭐라고 쓰느냐로 완전히 결정됨(자동 전파가
|
||||||
|
없기 때문에 성립하는 단순함).
|
||||||
|
|
||||||
|
### 3. 여러 modifier를 하나로 합치는 공개 유틸 필요 — `Modifier.Merge`(가칭)
|
||||||
|
|
||||||
|
caller가 named parameter 하나에 여러 modifier를 몰아넣고 싶을 때
|
||||||
|
(`Frame{modifier1, modifier2}`의 컴포넌트판)를 위해, 기존 flatten 규칙(배열
|
||||||
|
순서상 나중 것이 필드 단위로 이김, `modifier-plan.md` 2번)을 그대로 재사용하는
|
||||||
|
결합 함수를 공개 API로 노출: `Modifier.Merge(mod1, mod2, ...) -> Modifier`.
|
||||||
|
새 병합 규칙이 아니라 이미 확정된 flatten을 함수로 한 번 더 꺼내 쓸 수 있게
|
||||||
|
하는 것뿐 — **사용자 요청**("modifier를 합칠 방법도 존재한다면 좋을것
|
||||||
|
같아"). `MyComp { Modifier = Modifier.Merge(theme, override) }` → 컴포넌트
|
||||||
|
내부는 항상 이미 합쳐진 단일 값만 받으므로 컴포넌트 저작자가 배열 처리를
|
||||||
|
신경 쓸 필요 없음. Ref는 필드 충돌 개념이 없어 이 문제 자체가 없음(여러
|
||||||
|
Ref를 받으면 그냥 전부 실행하면 됨, `modifier-plan.md` §4-2) — 별도 결합
|
||||||
|
유틸 불필요.
|
||||||
|
|
||||||
|
## 남은 열린 질문 (`.claude/question.md`에도 취합, 전부 후순위 — 이름만 남음)
|
||||||
|
|
||||||
|
- **정확한 API 이름**: `Component`(플레인 함수 규약이라 별도 래퍼가 필요한지
|
||||||
|
자체도 불확실 — 아마 불필요), `GetSource` 계열 접근자 이름, `Source`
|
||||||
|
독립 생성자 이름, 컴포넌트 경계용 `props.Modifier`/`props.Ref` 필드명,
|
||||||
|
`Modifier.Merge` 함수명은 전부 가칭. `base/bind-system-plan.md`의 "남은
|
||||||
|
열린 질문" 절(정확한 함수/생성자 이름 미정)과 같은 급의 후순위 항목 —
|
||||||
|
구현 단계에서 다른 이름들과 함께 확정.
|
||||||
|
- **`quad2-try`는 확인 불필요로 재확인** — 진행이 중단된 상태라 이 논의와
|
||||||
|
무관.
|
||||||
|
|
@ -136,6 +136,18 @@ Modifier가 immutable해야 하는 이유(변환마다 clone)와 State가 이미
|
||||||
명시적으로 쓰는 구조라 "암묵적 분기"가 애초에 존재하지 않음 — 새로 결정할
|
명시적으로 쓰는 구조라 "암묵적 분기"가 애초에 존재하지 않음 — 새로 결정할
|
||||||
것 없음.
|
것 없음.
|
||||||
|
|
||||||
|
### 7. State가 Modifier를 값으로 담는 것은 UB — 타입으로 막을 것 (2026-08-04, 로드맵 인수인계 라운드)
|
||||||
|
|
||||||
|
Modifier "필드"가 State일 수 있는 것(4-1번)과는 별개로, **State 자체의
|
||||||
|
value가 Modifier인 경우**(예: `someState:With(fn)`이 Modifier를 반환)는
|
||||||
|
지원 대상이 아님 — Modifier는 "flatten해서 한 번 적용"이 전제인 정적 값인데,
|
||||||
|
State에 담기면 그 값이 반응형으로 바뀔 수 있다는 뜻이 되어 매번 재-flatten이
|
||||||
|
필요해지고, 이는 "정적 merge" 확정(1번)과 정면으로 충돌함 — **사용자
|
||||||
|
확정**("state 안에 modifier가 있으면 그건 끔찍히 힘들꺼야... 타입 상 받지
|
||||||
|
못하게 만들어야 할 수도 있고"). **UB로 확정, 가능하면 타입 시스템으로
|
||||||
|
아예 못 넣게 막을 것**(`State<Modifier>` 같은 조합을 타입 정의 단계에서
|
||||||
|
거부) — 런타임 가드가 아니라 타입 차단을 우선 검토.
|
||||||
|
|
||||||
## 열린 질문 (`.claude/question.md`에도 취합)
|
## 열린 질문 (`.claude/question.md`에도 취합)
|
||||||
|
|
||||||
- Getter 정확한 이름/모양(`:Get(key)` vs dot-access 겸용) — 후순위, 구현
|
- Getter 정확한 이름/모양(`:Get(key)` vs dot-access 겸용) — 후순위, 구현
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,27 @@ Slot은 하나의 instance 안에 여럿 존재할 수 있다. 전부 하나의
|
||||||
편하다는 방향. **기울어진 결론**: 별도 "Named Slot" 개념 없이, store나
|
편하다는 방향. **기울어진 결론**: 별도 "Named Slot" 개념 없이, store나
|
||||||
파라미터로 넘기고 그게 그냥 ref처럼 바인드되는 모양.
|
파라미터로 넘기고 그게 그냥 ref처럼 바인드되는 모양.
|
||||||
|
|
||||||
**상태 확인(2026-08-04 문서 정리 시점)**: 이 방향은 아래 "열린 질문" 절이나
|
**확정(2026-08-04, 로드맵 인수인계 라운드)**: 위 방향 그대로 확정 — 별도
|
||||||
`.claude/question.md`의 확정 목록 어디에도 명시적으로 흡수된 흔적이 없음 —
|
"Named Slot" 개념 없음, 슬롯 바인드 테이블을 store나 파라미터로 그냥 넘기면
|
||||||
아직 정식 확정 절차(`AskUserQuestion` 등)를 거치지 않은 것으로 보임. **열린
|
ref처럼 바인드됨 — **사용자 확정**("A. 맞음. 리프노드에선 그렇게 마운트됨").
|
||||||
질문으로 유지**, `.claude/question.md`에도 반영 필요.
|
단, 이 확정은 "리프 레벨에서 슬롯 하나가 마운트되는 방식"에 한정 — 여러
|
||||||
|
Slot이 형제로 섞이는 경우의 순서 보장 문제는 별도로 열려있음, 바로 아래
|
||||||
|
참고.
|
||||||
|
|
||||||
|
### 여러 Slot이 섞일 때 순서 보장 — 열린 질문 (2026-08-04 신규)
|
||||||
|
|
||||||
|
`Frame { Slot1, 일반자식, Slot2 }`처럼 Slot과 Slot 사이에 다른 요소가 끼거나
|
||||||
|
Slot이 여럿 형제로 존재할 때, 최종 자식 순서가 저작 순서(위쪽 Slot의 요소가
|
||||||
|
항상 아래쪽 Slot의 요소보다 앞)를 안정적으로 지키는지는 아직 설계 안 됨 —
|
||||||
|
**사용자 확정**("순서도 따름, 만약 웹이라면 순서에 맞춰서 마운트 되어야 하고,
|
||||||
|
아래쪽 slot은 위쪽 slot의 요소보다 무조건 아래 있어야함... 이게 가능하냐는
|
||||||
|
생각해봐야할 이야기임"). Roblox 자체는 z-순서를 주로 `LayoutOrder`/`ZIndex`로
|
||||||
|
푸는 편이라 당장 급하게 막히는 지점은 아니지만, `architecture.md`가 명시한
|
||||||
|
"quad-base는 다른 렌더 백엔드(GTK, 웹 DOM 등)에서도 재사용 가능해야 한다"는
|
||||||
|
전제와 직결되는 문제 — DOM류 백엔드는 형제 순서 자체가 렌더 순서라 이 보장이
|
||||||
|
없으면 못 씀. **후순위 열린 질문으로 유지**, Slot 코어 로직 구현 시점에
|
||||||
|
다시 볼 것 — M0/M1 단계를 막지는 않음(Roblox 단일 백엔드로는 당장 문제
|
||||||
|
없음).
|
||||||
|
|
||||||
## Slot과 Store 바인드의 관계 (`retract` 순서)
|
## Slot과 Store 바인드의 관계 (`retract` 순서)
|
||||||
|
|
||||||
|
|
@ -112,3 +129,8 @@ Slot은 바인딩되는 순간 그 안의 요소를 전부 own해버리는 데
|
||||||
- 재마운트 에러 처리(throw), retract 시 폐기(옮기지 않음) 둘 다 확정. 남은 건
|
- 재마운트 에러 처리(throw), retract 시 폐기(옮기지 않음) 둘 다 확정. 남은 건
|
||||||
실제 구현 단계에서 이 "폐기" 동작이 실사용에서 불편하지 않은지 재검증하는
|
실제 구현 단계에서 이 "폐기" 동작이 실사용에서 불편하지 않은지 재검증하는
|
||||||
정도 — 설계 방향 자체는 더 이상 열려있지 않음.
|
정도 — 설계 방향 자체는 더 이상 열려있지 않음.
|
||||||
|
- "클래스가 슬롯을 받는 방법"(Named Slot 없음)도 확정됨(위 "클래스가 슬롯을
|
||||||
|
받는 방법" 절 참고).
|
||||||
|
- **여러 Slot이 형제로 섞일 때 순서 보장**은 아직 열려있음(위 "여러 Slot이
|
||||||
|
섞일 때 순서 보장" 절 참고) — Roblox 단일 백엔드로는 급하지 않음, Slot
|
||||||
|
코어 로직 구현 시점에 재검토.
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,7 @@
|
||||||
|
|
||||||
## 지금 열려있는 것 (우선순위순)
|
## 지금 열려있는 것 (우선순위순)
|
||||||
|
|
||||||
### 1. [최우선] 컴포넌트 경계에서 modifier/Ref가 어떻게 전달되는가
|
### 1. 용어 정리 (사용자 요청, 진행 중)
|
||||||
|
|
||||||
사용자가 "지금 quad에서 가장 문제되는 부분"으로 직접 지목. 컴포넌트가
|
|
||||||
플레인 함수이고 반환하는 루트가 여러 개(혹은 Slot으로 갈라지는 구조)일 때,
|
|
||||||
호출부가 넘긴 modifier/Ref가 "어느 루트로 가야 하는지" 모호해지는 케이스가
|
|
||||||
있음. Jetpack Compose는 언어 강제가 아니라 "컴포저블은 `modifier` 파라미터를
|
|
||||||
받아 루트에 적용해야 한다"는 순수 관례(+린트)로 풂 — quad도 비슷한 관례
|
|
||||||
기반으로 갈 수 있어 보이나 다중 루트 케이스는 미정.
|
|
||||||
|
|
||||||
**주의**: modifier "값 자체"가 어떻게 동작하는지(정적 merge, immutable+clone
|
|
||||||
체이닝, State 필드 지원)는 이미 완전히 확정됨(`base/modifier-plan.md`) — 이
|
|
||||||
질문은 그것과 별개로 "경계를 어떻게 통과하느냐"만 다룸, 혼동하지 말 것.
|
|
||||||
|
|
||||||
→ 상세/배경: `research/component-composition-plan.md`.
|
|
||||||
|
|
||||||
### 2. 용어 정리 (사용자 요청, 진행 중)
|
|
||||||
|
|
||||||
사용자 원 메모: "quad는 register라던가 좀 부정확하거나 느낌이 바로 와닿지
|
사용자 원 메모: "quad는 register라던가 좀 부정확하거나 느낌이 바로 와닿지
|
||||||
않던 용어들이 많음 — 전체적 용어를 보고 생각해볼래? 제안을 줘, 나도 같이
|
않던 용어들이 많음 — 전체적 용어를 보고 생각해볼래? 제안을 줘, 나도 같이
|
||||||
|
|
@ -56,13 +41,17 @@
|
||||||
- `Store`/`Source`/`Modifier`/`Ref`/`process`/`retract`/`isHandlable`은
|
- `Store`/`Source`/`Modifier`/`Ref`/`process`/`retract`/`isHandlable`은
|
||||||
업계 선례와 잘 맞거나 이미 신중하게 결정된 이름들이라 특별한 문제 없음.
|
업계 선례와 잘 맞거나 이미 신중하게 결정된 이름들이라 특별한 문제 없음.
|
||||||
|
|
||||||
### 3. 낮은 우선순위
|
### 2. 낮은 우선순위
|
||||||
|
|
||||||
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
|
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
|
||||||
착수를 막지 않음.
|
착수를 막지 않음.
|
||||||
- **v1 `objectListClass.__newIndex` 오타 기능의 재현 테스트 필요** —
|
- **v1 `objectListClass.__newIndex` 오타 기능의 재현 테스트 필요** —
|
||||||
`base/quad-v1-architecture.md`에 남겨진 v1 내부 동작 확인 사항, 마이그레이션
|
`base/quad-v1-architecture.md`에 남겨진 v1 내부 동작 확인 사항, 마이그레이션
|
||||||
가이드 작성 시점에 필요. 지금은 그냥 백로그로만 기록.
|
가이드 작성 시점에 필요. 지금은 그냥 백로그로만 기록.
|
||||||
|
- **여러 Slot이 형제로 섞일 때 순서 보장** — `base/slot-plan.md`의 "여러
|
||||||
|
Slot이 섞일 때 순서 보장" 절 참고. Roblox 단일 백엔드로는 급하지 않음
|
||||||
|
(LayoutOrder/ZIndex로 대부분 해결), 다른 백엔드(웹 DOM 등) 재사용성과
|
||||||
|
직결되는 문제라 Slot 코어 로직 구현 시점에 재검토.
|
||||||
|
|
||||||
## 참고: 지금까지 확정된 것 (요약)
|
## 참고: 지금까지 확정된 것 (요약)
|
||||||
|
|
||||||
|
|
@ -79,6 +68,7 @@
|
||||||
| Slot 재조정, 재마운트 시 throw, retract=폐기 | `base/slot-plan.md` |
|
| Slot 재조정, 재마운트 시 throw, retract=폐기 | `base/slot-plan.md` |
|
||||||
| `Connected`+GC 라이프사이클 패턴 | `base/lifecycle-pattern.md` |
|
| `Connected`+GC 라이프사이클 패턴 | `base/lifecycle-pattern.md` |
|
||||||
| Modifier(정적 merge, immutable 체이닝, State 필드 지원) | `base/modifier-plan.md` |
|
| Modifier(정적 merge, immutable 체이닝, State 필드 지원) | `base/modifier-plan.md` |
|
||||||
|
| 컴포넌트화(플레인 함수, State/Source 경계, 컴포넌트 경계 modifier/Ref는 named parameter로 전달, multi-root 개념 폐기, `Modifier.Merge`) | `base/component-composition-plan.md` |
|
||||||
| 컴포넌트 이식성(전역 store 참조 시 재사용성 문제) | `base/purity-and-effects-plan.md` |
|
| 컴포넌트 이식성(전역 store 참조 시 재사용성 문제) | `base/purity-and-effects-plan.md` |
|
||||||
| Fusion/Vide 비교 리서치(주의: 일부 서술은 이후 라운드에서 뒤집힘, 문서 내 정정 표시 참고) | `base/comparison-fusion-vide.md` |
|
| Fusion/Vide 비교 리서치(주의: 일부 서술은 이후 라운드에서 뒤집힘, 문서 내 정정 표시 참고) | `base/comparison-fusion-vide.md` |
|
||||||
| v1 내부 동작 스냅샷 | `base/quad-v1-architecture.md` |
|
| v1 내부 동작 스냅샷 | `base/quad-v1-architecture.md` |
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
# 컴포넌트화 (Roblox 기본 오브젝트 이외의 사용자 정의 컴포넌트)
|
|
||||||
|
|
||||||
**상태**: research — 2026-08-04 세션 채팅 논의에서 핵심 골격 수렴, 세부
|
|
||||||
API 이름/modifier·Ref passthrough는 미정. 사용자가 "지금 quad에서 가장
|
|
||||||
문제되는 부분"으로 직접 지목한 주제. `base/bind-system-plan.md`의
|
|
||||||
Store/State/Source 온톨로지가 먼저 확정된 뒤에야 이 논의가 열림 — 그
|
|
||||||
문서가 선행 컨텍스트.
|
|
||||||
|
|
||||||
## 문제
|
|
||||||
|
|
||||||
v1의 `Class.Extend()`(Init/Render/AfterRender/Getter/Setter/UpdateTriggers,
|
|
||||||
`base/quad-v1-architecture.md` 참고)는 이미 OOP 상속 스타일이라 폐기
|
|
||||||
방향이지만, 그게 제공하던 실제 편의 기능(컴포넌트가 자기 store를 자동으로
|
|
||||||
가짐, props로 넘어온 State를 자동 흡수, `self:Default`/`self "key"`로
|
|
||||||
기본값·바인딩)까지 같이 버려도 되는지가 미결이었음. `MyComp {...}` 형태로
|
|
||||||
호출되는 사용자 정의 컴포넌트를 v2에서 어떤 모양으로 작성하게 할지가 핵심
|
|
||||||
질문.
|
|
||||||
|
|
||||||
## v1 실제 메커니즘 (조사 완료, `quad.qwreey.kr` 튜토리얼 + `initreq/quad/src/` 소스로 교차검증)
|
|
||||||
|
|
||||||
- `myStore "key"` → register(현재 State에 해당) 반환. `:Default(v)`/
|
|
||||||
`:With(fn)`/`:Add(v)`/`:Tween(opts)` 체이닝 가능(`store.lua:433-457`).
|
|
||||||
- `Class.Extend()`의 `:Init(props)`에서 `props:Default("Size", v)`로 기본값
|
|
||||||
설정, props 테이블 자체가 store 인스턴스로 변신(`class.lua:365-379`,
|
|
||||||
`storeNew(prop,nil)`).
|
|
||||||
- props로 넘어온 값이 State(`quad_register`)면 `initStoreRegisterBinding`
|
|
||||||
(`store.lua:394-431`)이 자동으로 감지해 컴포넌트 자신의 store 키에 재귀
|
|
||||||
연결 — **자동 흡수 매직**이 실제로 존재했음.
|
|
||||||
- `self(name)` linker가 **두 가지 역할**을 겸함: (1) `self "_button"`을
|
|
||||||
자식 자리에 넣으면 렌더링된 인스턴스를 `self._button`에 즉시 잡아둠(Ref
|
|
||||||
역할) (2) `[Event.Prop "Text"] = self "Text"`로 인스턴스 프로퍼티 변경을
|
|
||||||
다시 컴포넌트 store로 역방향 전파(양방향 바인딩, `EmitPropertyChangedSignal`
|
|
||||||
자동 연결과 동일) — quad.qwreey.kr 튜토리얼 `11_extend/` 문서 원문 확인.
|
|
||||||
|
|
||||||
이 두 역할이 v2 온톨로지에서는 이미 갈라져 있음: (1)은 확정된 **Ref**가
|
|
||||||
대체, (2)는 이번 논의에서 다루는 Source 양방향 프록시가 대체.
|
|
||||||
|
|
||||||
## 수렴된 결론
|
|
||||||
|
|
||||||
### 1. 컴포넌트 = 그냥 함수, "자기 store 자동 소유" 매직은 폐기
|
|
||||||
|
|
||||||
`MyComp = function(props) return Frame {...} end`, 호출 규약은
|
|
||||||
`Frame{...}`와 동일(`MyComp{...}` → `MyComp(propsTable)`). v1의 Extend
|
|
||||||
자동-store-생성+자동-흡수 매직은 재현하지 않음 — 대신 React식으로 호출부가
|
|
||||||
State/raw/Source/콜백 중 뭘 넘길지 명시적으로 고름. 이유: 자동 흡수는
|
|
||||||
매 컴포넌트 호출마다 "이 prop이 State인가?" 타입 분기를 프레임워크가
|
|
||||||
암묵적으로 수행해야 하는 매직이고, 명시적 전달이 더 단순·예측 가능(React가
|
|
||||||
Vue/Svelte 대비 내세우는 강점과 동일 논리) — **사용자 확정**("마법 안쓴다
|
|
||||||
그것도 동의함").
|
|
||||||
|
|
||||||
### 2. State/Source 경계 규칙: 파생이면 읽기전용, 원본이면 쓰기 가능
|
|
||||||
|
|
||||||
State는 `:With`/`:Compute`로 만들어진 파생값일 수 있어 쓰기가 정의 자체가
|
|
||||||
안 됨. Source(독립이든 Store 소속 `StoreSource` 프록시든)는 파생이 아니라
|
|
||||||
항상 원본 슬롯 하나를 직접 가리키므로 쓰기가 의미 있음 — **사용자 확정**
|
|
||||||
("맞음. 확실해").
|
|
||||||
|
|
||||||
### 3. `StoreSource`: Source를 인터페이스+구현체로 두고, Store 키에서 그 인터페이스를 구현하는 얇은 프록시를 받음
|
|
||||||
|
|
||||||
- **Source = 인터페이스이자 구현체**: 독립 생성자 `Source(initial)`가 기본
|
|
||||||
구현체, `store:GetSource("key")`(가칭)류 접근자가 반환하는 값은 같은
|
|
||||||
인터페이스를 구현하는 별도의 얇은 프록시(`StoreSource`) — 읽기는
|
|
||||||
`store.key`로, 쓰기는 `store.key = v`로 위임. **내부 Source 객체를 그대로
|
|
||||||
노출하지 않음** — 그러면 "쓰기는 오직 Store의 `__newindex`뿐"이라는 기존
|
|
||||||
확정과 새 쓰기 경로가 충돌하게 됨.
|
|
||||||
- **캐시 안 함**: State가 이미 "매번 새로 만듦, store에 캐시 안 됨"으로
|
|
||||||
확정돼 있어 일관성 + 엔지니어링 비용 둘 다 이쪽이 쌈 — **사용자 확정**
|
|
||||||
("그냥 엔지니어링적으로 비용이 싼거 택해").
|
|
||||||
|
|
||||||
### 4. Source 직접 전달(양방향)은 핸들러 계약 확장 없이 타입 유니온으로 처리 — 단, 실사용 범위는 좁음
|
|
||||||
|
|
||||||
- 핸들러가 값을 받을 때 `Source<T> | State<T>` 유니온으로 받고, 내부에서
|
|
||||||
타입 체크만 하면 됨(Source면 인스턴스 변경 이벤트에 걸어 역방향 쓰기까지
|
|
||||||
처리, State면 읽기만) — `isHandlable`/`priority`/`process`/`retract` 4종
|
|
||||||
계약에 5번째 항목을 추가할 필요 없음. Source 자체가 계산이 없는 원천이라
|
|
||||||
가능한 단순화 — **사용자 확정**("그냥 타입 상 source를 받거나 state를
|
|
||||||
받거나 하면 됨. source 자체는 원천이라 컴퓨팅 같은거 없어").
|
|
||||||
- **하지만 실사용은 좁을 것으로 예상**: `isEnabled`처럼 여러 조건에 영향
|
|
||||||
받는(=파생된) 값은 애초에 State지 Source가 아니므로 이 경로로 못 넘김.
|
|
||||||
즉 Source 직접 전달이 통하는 건 진짜 단순한 1:1 원본-토글 케이스뿐이고,
|
|
||||||
일반적인 경우엔 React식 `value(State) + onChange(callback)` 패턴이 기본
|
|
||||||
— **사용자 확정**("isenabled가 여러 조건에 영향 받으면 바로 문제가
|
|
||||||
생기는거지. 따라서 실제 사용은 제한적일듯. callback을 쓰는게
|
|
||||||
일반적이여 보이긴 해. 타입으로도 편하기도 하고 디버깅도 편함").
|
|
||||||
|
|
||||||
### 5. 리프(Roblox 프로퍼티) 바인딩엔 원칙적으로 State만
|
|
||||||
|
|
||||||
계산된 최종값만 실제로 인스턴스에 반영되어야 하므로, 리프 바인딩은 State가
|
|
||||||
일반 경로. Source는 리프 바인딩용 프리미티브가 아니라, 아주 단순한 구조에서
|
|
||||||
콜백 보일러플레이트를 줄이기 위한 좁은 용도의 예외 — **사용자 확정**
|
|
||||||
("리프 바인딩엔 state만 쓰이지 않을까... source는 그냥 아주 단순한
|
|
||||||
구조에서 콜백을 넣고 하는 복잡함을 줄이기 위함일 뿐임").
|
|
||||||
|
|
||||||
## 아직 열린 질문 (`.claude/question.md`에도 취합)
|
|
||||||
|
|
||||||
- **modifier/Ref가 컴포넌트 경계를 어떻게 통과하는가**: 컴포넌트가 플레인
|
|
||||||
함수이고 반환하는 루트가 여러 개(혹은 Slot으로 갈라지는 구조)일 때, 호출부가
|
|
||||||
넘긴 modifier/Ref가 "어느 루트로 가야 하는지" 모호해지는 케이스가 있음.
|
|
||||||
Jetpack Compose는 언어 강제가 아니라 "컴포저블은 `modifier` 파라미터를
|
|
||||||
받아 루트에 적용해야 한다"는 순수 관례(+린트)로 풂 — quad도 비슷한 관례
|
|
||||||
기반으로 갈 수 있어 보이나 다중 루트 케이스는 미정. **주의: 이건 "경계를
|
|
||||||
어떻게 통과하는가"의 문제이고, "modifier 값 자체가 어떻게 동작하는가"(정적
|
|
||||||
merge, immutable 체이닝)는 `research/modifier-plan.md`로 이미 별도 확정됨
|
|
||||||
— 둘을 혼동하지 말 것.**
|
|
||||||
- **정확한 API 이름**: `Component`(플레인 함수 규약이라 별도 래퍼가 필요한지
|
|
||||||
자체도 불확실 — 아마 불필요), `GetSource` 계열 접근자 이름, `Source`
|
|
||||||
독립 생성자 이름은 전부 가칭. `base/bind-system-plan.md`의 "남은 열린
|
|
||||||
질문" 절(정확한 함수/생성자 이름 미정)과 같은 급의 후순위 항목.
|
|
||||||
- **`quad2-try`는 확인 불필요로 재확인** — 진행이 중단된 상태라 이 논의와
|
|
||||||
무관.
|
|
||||||
97
CLAUDE.md
97
CLAUDE.md
|
|
@ -22,11 +22,12 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
|
||||||
**지금은 설계/계획 단계이고 구현은 아직 시작 전** — 저장소 루트에 실제 소스
|
**지금은 설계/계획 단계이고 구현은 아직 시작 전** — 저장소 루트에 실제 소스
|
||||||
코드(`src/` 등)가 없음. 핵심 아키텍처(Store 책임 분리, `process`/`retract`
|
코드(`src/` 등)가 없음. 핵심 아키텍처(Store 책임 분리, `process`/`retract`
|
||||||
디스패치 모델, Store/State/Source 온톨로지, 소스 트리 구조, Modifier 메커니즘,
|
디스패치 모델, Store/State/Source 온톨로지, 소스 트리 구조, Modifier 메커니즘,
|
||||||
컴포넌트=플레인 함수)는 전부 `.claude/base/`에 문서로 확정돼 있음 — 먼저
|
컴포넌트=플레인 함수, 컴포넌트 경계 modifier/Ref 전달)는 전부 `.claude/base/`에
|
||||||
`.claude/base/architecture.md`를 읽을 것. **단, "핵심 설계 질문이 더 이상
|
문서로 확정돼 있음 — 먼저 `.claude/base/architecture.md`를 읽을 것. 사용자가
|
||||||
없다"는 뜻은 아님** — 컴포넌트화(특히 modifier/Ref가 컴포넌트 경계를 어떻게
|
직접 "지금 quad에서 가장 문제되는 부분"으로 지목했던 컴포넌트화(특히
|
||||||
통과하는지)는 사용자가 직접 "지금 quad에서 가장 문제되는 부분"으로 지목한
|
modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션에서 수렴
|
||||||
채 아직 열려있음, 아래 "지금 할 일" 참고.
|
완료(`base/component-composition-plan.md`) — 남은 핵심 설계 질문은 없고,
|
||||||
|
용어 정리(진행 중)와 실제 스캐폴딩만 남음, 아래 "지금 할 일" 참고.
|
||||||
|
|
||||||
이전에 시도했다 폐기한 v2 재작성 시도(`.claude/initreq/quad2-try`)도 리서치
|
이전에 시도했다 폐기한 v2 재작성 시도(`.claude/initreq/quad2-try`)도 리서치
|
||||||
완료 — OOP 상속/커스텀 파서/Slot 스텁/`Pipe` copy-on-write 절충안은 확인된
|
완료 — OOP 상속/커스텀 파서/Slot 스텁/`Pipe` copy-on-write 절충안은 확인된
|
||||||
|
|
@ -39,8 +40,8 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
|
||||||
`.claude/base/architecture.md`를 읽을 것.
|
`.claude/base/architecture.md`를 읽을 것.
|
||||||
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의. 지금은
|
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의. 지금은
|
||||||
`tween-plan.md`(세부 옵션만 남음), `existing-instance-bind-plan.md`(급하지
|
`tween-plan.md`(세부 옵션만 남음), `existing-instance-bind-plan.md`(급하지
|
||||||
않음), `component-composition-plan.md`(**사용자가 최우선으로 지목한 열린
|
않음) 두 개뿐 — `component-composition-plan.md`는 2026-08-04 세션에 수렴
|
||||||
주제**) 세 개뿐.
|
완료돼 `base/`로 승격됨.
|
||||||
- `.claude/qa-request/`, `.claude/archive/`, `.claude/feedback/` — 구현
|
- `.claude/qa-request/`, `.claude/archive/`, `.claude/feedback/` — 구현
|
||||||
시작되면 쓰기 시작함, 지금은 비어있음.
|
시작되면 쓰기 시작함, 지금은 비어있음.
|
||||||
- `.claude/initreq/` — 클론해둔 참고 레포(quad v1, Fusion, Vide, rbvm, tbox,
|
- `.claude/initreq/` — 클론해둔 참고 레포(quad v1, Fusion, Vide, rbvm, tbox,
|
||||||
|
|
@ -48,6 +49,9 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
|
||||||
`.gitignore`로 커밋 제외됨** — 내용을 다른 곳으로 옮기지 말고 항상 원본
|
`.gitignore`로 커밋 제외됨** — 내용을 다른 곳으로 옮기지 말고 항상 원본
|
||||||
그대로 둘 것. 리서치가 더 필요하면 이 폴더를 다시 파고들 것.
|
그대로 둘 것. 리서치가 더 필요하면 이 폴더를 다시 파고들 것.
|
||||||
- `.claude/question.md` — 사용자가 답해야 할 질문 전체 취합(우선순위순).
|
- `.claude/question.md` — 사용자가 답해야 할 질문 전체 취합(우선순위순).
|
||||||
|
- 루트 `ROADMAP.md` — 설계 단계 종료 후 실제 구현 순서(M0, M1, ... 마일스톤 +
|
||||||
|
todo 체크박스). "무엇을 확정했는가"는 `.claude/base/`가 소스, "어떤 순서로
|
||||||
|
만드는가"는 이 문서가 소스 — 헷갈리지 말 것.
|
||||||
- 루트 `HUMAN_TODO.md` — 사람만 할 수 있는 일(로컬 GUI 조작, 스케줄/루프
|
- 루트 `HUMAN_TODO.md` — 사람만 할 수 있는 일(로컬 GUI 조작, 스케줄/루프
|
||||||
설정 등).
|
설정 등).
|
||||||
|
|
||||||
|
|
@ -87,28 +91,31 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
|
||||||
|
|
||||||
## 지금 할 일 (우선순위순)
|
## 지금 할 일 (우선순위순)
|
||||||
|
|
||||||
1. **컴포넌트화 논의 계속 — 사용자가 직접 "가장 문제되는 부분"으로 지목.**
|
1. **구현 시작 — 루트 `ROADMAP.md`의 M0부터.** 설계 단계는 2026-08-04
|
||||||
`research/component-composition-plan.md` 참고. 핵심 골격(컴포넌트=플레인
|
로드맵 인수인계 라운드로 종료, 다음 세션은 바로 `ROADMAP.md` M0(스켈레톤+
|
||||||
함수, State/Source 읽기·쓰기 경계, `StoreSource` 프록시)과 Modifier
|
기술검증 스파이크)부터 시작. M0는 "진짜" 마일스톤이 아니라, 지금까지
|
||||||
메커니즘 자체(`base/modifier-plan.md`, 완전 확정)는 수렴됨 — 남은 건
|
추론만으로 확정하고 실제 Luau로 부딪혀본 적 없는 세 가지(Store/State
|
||||||
**modifier/Ref가 컴포넌트 경계(특히 다중 루트)를 어떻게 통과하는가**라는
|
propagation, 재귀 process/retract 디스패치, 컴포넌트 경계 named-parameter
|
||||||
진짜 설계 질문(이름 문제가 아님). 다음 세션에서 이걸 이어서 파고들 것.
|
전달)를 던지는 코드로 검증하는 단계 — 여기서 걸리면 `base/` 문서를 그
|
||||||
2. **실제 스캐폴딩.** 소스 트리 구조는 문서로 이미 확정됨(`base/
|
자리에서 고치는 게 정상. M0 통과 후 M1(실제 스캐폴딩: `quad-base/`,
|
||||||
architecture.md`의 "구현 착수: 소스 트리 구조 확정" 절) — `quad-base/`,
|
`quad-roblox/` 폴더 + `wally.toml`/`default.project.json`/`.luaurc` +
|
||||||
`quad-roblox/` 폴더, 각각의 `wally.toml`, 루트 `default.project.json`,
|
quad-base용 최소 mock 테스트 하네스)으로 진행 — 소스 트리 자체는 이미
|
||||||
`.luaurc`를 만들 것. 1번의 컴포넌트 경계 논의가 `DI`/`Modifier` 모듈
|
확정됨(`base/architecture.md` "구현 착수" 절). 이 단계부터
|
||||||
설계에 영향을 주므로, 그 결론이 안 나온 상태에서도 나머지 구조(Store/
|
`qa-request/`/`archive/` 폴더가 실제로 쓰이기 시작함. **세부 순서/todo는
|
||||||
State/Source, 디스패치 엔진, Slot)는 그대로 스캐폴딩 가능 — 막을 필요
|
`ROADMAP.md`가 소스** — 여기서 반복 안 함.
|
||||||
없음(`architecture.md`에도 명시). 이 시점부터 `qa-request/`/`archive/`
|
2. **용어 정리 — 사용자가 별도로 요청, 진행 중.** "register"(v1) 같이
|
||||||
폴더가 실제로 쓰이기 시작함.
|
|
||||||
3. **용어 정리 — 사용자가 별도로 요청, 진행 중.** "register"(v1) 같이
|
|
||||||
부정확한 이름들을 전체적으로 재검토하자는 요청 — 1차 제안 완료(우선순위
|
부정확한 이름들을 전체적으로 재검토하자는 요청 — 1차 제안 완료(우선순위
|
||||||
순: `State`가 React/Vue식 "쓸 수 있는 로컬 상태"라는 통상 의미와 반대라
|
순: `State`가 React/Vue식 "쓸 수 있는 로컬 상태"라는 통상 의미와 반대라
|
||||||
가장 위험, `DI`가 Dependency Injection 축약어와 충돌, `PerInstanceState`가
|
가장 위험, `DI`가 Dependency Injection 축약어와 충돌, `PerInstanceState`가
|
||||||
핵심 프리미티브 `State`와 이름 충돌 — 세부는 `.claude/question.md` 참고),
|
핵심 프리미티브 `State`와 이름 충돌 — 세부는 `.claude/question.md` 참고),
|
||||||
사용자와 같이 계속 논의 필요.
|
사용자와 같이 계속 논의 필요. 컴포넌트 경계용 `props.Modifier`/`props.Ref`/
|
||||||
4. `research/existing-instance-bind-plan.md`는 급하지 않음 — 스코프 논의만
|
`Modifier.Merge` 같은 새 가칭들도 이 정리에 합류 대상.
|
||||||
|
3. `research/existing-instance-bind-plan.md`는 급하지 않음 — 스코프 논의만
|
||||||
필요, 구현 착수를 막지 않음.
|
필요, 구현 착수를 막지 않음.
|
||||||
|
4. **[백로그] 범용 렌더 디버깅 도구로서의 quad-mock.** 1번의 quad-base 테스트용
|
||||||
|
mock과는 별개 — 정적 스냅샷을 넘어 Tween mock 같은 동적 동작까지 지원하는
|
||||||
|
더 큰 스코프의 디버깅 도구(`architecture.md` "테스트 전략" 절 백로그 참고).
|
||||||
|
효용성 봐가며 나중에 검토, 지금 당장 설계할 필요 없음.
|
||||||
5. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
|
5. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
|
||||||
(`HUMAN_TODO.md` 2번 항목).
|
(`HUMAN_TODO.md` 2번 항목).
|
||||||
|
|
||||||
|
|
@ -125,12 +132,19 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
|
||||||
경우의 setter/getter 동작까지 전부 확정(`base/modifier-plan.md`, 새로
|
경우의 setter/getter 동작까지 전부 확정(`base/modifier-plan.md`, 새로
|
||||||
base 승격). 이 논의에서 "관측해야 실체화된다"는 프로젝트 전역 원칙도
|
base 승격). 이 논의에서 "관측해야 실체화된다"는 프로젝트 전역 원칙도
|
||||||
명문화(`bind-system-plan.md`).
|
명문화(`bind-system-plan.md`).
|
||||||
- **컴포넌트화 논의 시작, 아직 미완** — v1의 `Class.Extend()` 자동-store
|
- **컴포넌트화 논의, 같은 날 후속 세션에서 완결** — v1의 `Class.Extend()`
|
||||||
매직은 폐기하고 React식으로 값을 명시적으로 전달하는 방향으로 수렴,
|
자동-store 매직은 폐기하고 React식으로 값을 명시적으로 전달하는 방향으로
|
||||||
`StoreSource`(Source를 인터페이스+구현체로 보고 Store 키에서 얇은
|
수렴, `StoreSource`(Source를 인터페이스+구현체로 보고 Store 키에서 얇은
|
||||||
프록시로 얻는 것) 아이디어까지 나왔지만 modifier/Ref의 컴포넌트 경계
|
프록시로 얻는 것) 아이디어 확정. 마지막 미결이던 "modifier/Ref의 컴포넌트
|
||||||
통과 방식은 미해결(`research/component-composition-plan.md`, 위 "지금
|
경계 통과"도 후속 세션에서 풀림: Compose/Fusion/Vide/v1 4개 선례를
|
||||||
할 일" 1번).
|
서브에이전트로 병렬 조사한 결과 전부 named parameter로 경계를 넘기는
|
||||||
|
패턴에 수렴한다는 게 확인됐고, "컴포넌트가 여러 루트를 반환한다"는
|
||||||
|
프레이밍 자체가 (a) Luau가 tail position 밖 다중 리턴을 지원 안 해서
|
||||||
|
불필요한 개념과 (b) 이미 있는 Slot 메커니즘을 섞은 것이었음이 드러나
|
||||||
|
정리됨 — 결론: 경계는 named parameter(`props.Modifier`/`props.Ref`
|
||||||
|
가칭), "다중 루트"라는 별도 개념은 폐기, 여러 modifier를 하나로 합치는
|
||||||
|
`Modifier.Merge`(가칭) 유틸 추가. `research/component-composition-plan.md`
|
||||||
|
→ `base/component-composition-plan.md`로 승격 완료.
|
||||||
- **문서 전체 감사 및 정리** — `.claude/` 코퍼스 전체(약 15개 문서)를
|
- **문서 전체 감사 및 정리** — `.claude/` 코퍼스 전체(약 15개 문서)를
|
||||||
서브에이전트로 감사해 여러 라운드에 걸쳐 쌓인 모순/중복/stale 마커를
|
서브에이전트로 감사해 여러 라운드에 걸쳐 쌓인 모순/중복/stale 마커를
|
||||||
대거 발견하고 수정(예: 이벤트 dot-access 확정 여부가 문서 내에서 서로
|
대거 발견하고 수정(예: 이벤트 dot-access 확정 여부가 문서 내에서 서로
|
||||||
|
|
@ -142,4 +156,25 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
|
||||||
git log와 각 `base/`/`research/` 문서 안의 라운드 표시(예: "2026-08-04
|
git log와 각 `base/`/`research/` 문서 안의 라운드 표시(예: "2026-08-04
|
||||||
3차 라운드")를 참고할 것, 여기서 전부 반복하지 않음.
|
3차 라운드")를 참고할 것, 여기서 전부 반복하지 않음.
|
||||||
|
|
||||||
용어 정리 제안 진행 중인 점은 위 "지금 할 일" 3번 참고.
|
**같은 날 로드맵 인수인계 라운드 — 설계 단계 마무리, 구현 준비 완료**:
|
||||||
|
- **quad-base 테스트 mock 방향 확정**: Vide 선례(`test/mock.luau`, ~300줄,
|
||||||
|
순수 `luau` CLI, Studio 불필요) 그대로 채택, 스코프는 정적 디버깅 한정(Tween
|
||||||
|
같은 동적 동작 제외), quad-roblox로 작성한 컴포넌트가 mock에서도 그대로
|
||||||
|
돌아가야 한다는 요구 없음(단순하게 감) — `architecture.md` "테스트 전략"
|
||||||
|
절. 범용 렌더 디버깅 도구(Tween mock 포함)는 별개로 백로그.
|
||||||
|
- **구현 전 리스크 감사**: `.claude/base/` 전체 + 남은 `research/`를
|
||||||
|
서브에이전트로 감사해 "실제 Luau 접촉 없이 추론만으로 확정된 것" 3개
|
||||||
|
(Store/State 반응형 코어, 디스패치 엔진, 컴포넌트 경계 modifier/Ref)를
|
||||||
|
식별 — 이것들은 M0 스파이크로 검증하기로 함(아래). 감사 중 `slot-plan.md`가
|
||||||
|
스스로 "정식 확정 안 됨"이라 표시해뒀던 "클래스가 슬롯을 받는 방법"(Named
|
||||||
|
Slot 없음)도 이번에 정식 확정, 대신 "여러 Slot이 형제로 섞일 때 순서 보장"
|
||||||
|
이라는 새 하위 질문이 열림(다중 백엔드 관점, Roblox만이면 급하지 않음).
|
||||||
|
`State<Modifier>` 조합은 UB로 확정해 타입으로 막기로 함(`modifier-plan.md`
|
||||||
|
7번), 디스패치 엔진의 일반적 무한루프는 방어 로직 없이 provider 버그로
|
||||||
|
간주하기로 확정(`bind-system-plan.md`).
|
||||||
|
- **루트 `ROADMAP.md` 신설** — M0(스켈레톤+기술검증 스파이크, "진짜"
|
||||||
|
마일스톤 아님)부터 M11(Tween)까지 + 병행 가능 항목 + 백로그로 구성된 실행
|
||||||
|
계획, todo 체크박스 포함. 오늘은 문서 준비만 — **다음 세션이 M0부터 실제
|
||||||
|
시작**.
|
||||||
|
|
||||||
|
용어 정리 제안 진행 중인 점은 위 "지금 할 일" 2번 참고.
|
||||||
|
|
|
||||||
116
ROADMAP.md
Normal file
116
ROADMAP.md
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
# ROADMAP.md
|
||||||
|
|
||||||
|
quad-v2 구현 단계 실행 계획. 설계 근거/아키텍처 자체는 여기 안 옮겨적음 —
|
||||||
|
`.claude/base/`가 소스, 여긴 **순서와 진행 상황**만. 마일스톤 시작할 때
|
||||||
|
체크박스를 세분화해서 늘려도 되고, 끝나면 체크만 하면 됨 — 살아있는 문서.
|
||||||
|
|
||||||
|
**2026-08-04 세션에 준비만 해둔 상태 — 아직 M0도 시작 안 함.** 다음 세션은
|
||||||
|
바로 M0부터.
|
||||||
|
|
||||||
|
## M0 — 스켈레톤 + 기술검증 (스파이크, "진짜" 마일스톤 아님)
|
||||||
|
|
||||||
|
최종 소스 트리를 그대로 만들기 전에, 지금까지 **추론만으로 확정하고 실제
|
||||||
|
Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검증하는 단계 —
|
||||||
|
`.claude/base/` 감사에서 나온 결론(2026-08-04). 여기서 뭔가 어긋나면
|
||||||
|
`architecture.md`/`bind-system-plan.md` 등을 이 시점에 고치는 게 정상 —
|
||||||
|
실패가 아니라 이 단계의 목적.
|
||||||
|
|
||||||
|
- [ ] Store/State push-invalidate → pull-recompute propagation을 실제로
|
||||||
|
짜보기(다이아몬드 의존성 케이스 포함 — 이미 invalid면 전파 중단되는지)
|
||||||
|
- [ ] `process`/`retract` 재귀 재-process 디스패치를 실제로 짜보기(store-bind
|
||||||
|
핸들러 하나 + `isHandlable` 우선순위 스캔 포함)
|
||||||
|
- [ ] `props.Modifier`/`props.Ref` named-parameter로 받는 컴포넌트 하나 작성,
|
||||||
|
`export type Params = {...}`로 타입 체크되는지 확인
|
||||||
|
(`component-composition-plan.md` 최종 결론 1번)
|
||||||
|
- [ ] 위 과정에서 소스 트리/메커니즘 문서에 고칠 부분이 생기면 그 자리에서
|
||||||
|
`.claude/base/` 갱신
|
||||||
|
|
||||||
|
**통과 기준**: 세 개 다 Luau에서 자연스럽게 짜이는 게 확인되면 M1 진행.
|
||||||
|
안 되면 여기서 관련 `base/` 문서부터 고치고 재시도.
|
||||||
|
|
||||||
|
## M1 — 실제 스캐폴딩
|
||||||
|
|
||||||
|
- [ ] `quad-base/`, `quad-roblox/` 폴더 + 각 `wally.toml`
|
||||||
|
- [ ] 루트 `default.project.json`, `.luaurc`(`architecture.md` "구현 착수:
|
||||||
|
소스 트리 구조 확정" 절 그대로)
|
||||||
|
- [ ] quad-base용 최소 mock 테스트 하네스(Vide `test/mock.luau` 선례, 순수
|
||||||
|
`luau` CLI, `architecture.md` "테스트 전략" 절 참고)
|
||||||
|
- [ ] 이 시점부터 `.claude/qa-request/`/`.claude/archive/` 폴더 실사용 시작
|
||||||
|
|
||||||
|
## M2 — 디스패치 엔진
|
||||||
|
|
||||||
|
- [ ] `Dispatch/init.luau`(`process`/`retract` 엔진, `isHandlable` 우선순위 스캔)
|
||||||
|
- [ ] `Handler.luau`(핸들러 계약 타입)
|
||||||
|
- [ ] mock 대상 테스트
|
||||||
|
|
||||||
|
## M3 — Store/State/Source
|
||||||
|
|
||||||
|
- [ ] `Source.luau`/`State.luau`/`Store.luau`
|
||||||
|
- [ ] `store.key` dot-access 타입 추론 확인
|
||||||
|
- [ ] mock 대상 테스트
|
||||||
|
|
||||||
|
## M4 — 첫 end-to-end 반응형 업데이트
|
||||||
|
|
||||||
|
- [ ] `Dispatch/StoreBind.luau`(재귀 재실행 로직, 엔진 무관)
|
||||||
|
- [ ] mock 대상으로 "store 값 바꾸면 `process`가 다시 호출된다" 확인
|
||||||
|
|
||||||
|
## M5 — quad-roblox 최소 프로바이더
|
||||||
|
|
||||||
|
- [ ] `RobloxFactory.luau`(BaseModule 뮤테이션, 재호출 가드)
|
||||||
|
- [ ] `DI/init.luau`(제네릭 생성자 + ~25개 정적 필드)
|
||||||
|
- [ ] `Handlers/Property.luau`, `Handlers/InstanceChild.luau`
|
||||||
|
- [ ] 실제 Roblox에서 첫 `Frame{...}` 렌더 확인 — **Studio 작업이라
|
||||||
|
`HUMAN_TODO.md` 1번(계정 분리) 먼저 되어야 진행 가능, `SAFETY.md` 준수**
|
||||||
|
|
||||||
|
## M6 — Slot
|
||||||
|
|
||||||
|
- [ ] "여러 Slot이 형제로 섞일 때 순서 보장" 열린 질문 확인(`slot-plan.md`) —
|
||||||
|
Roblox 단일 백엔드로는 급하지 않으면 스킵하고 진행 가능
|
||||||
|
- [ ] base `Dispatch/Slot.luau`(추상 재조정) + quad-roblox `Handlers/Slot.luau`
|
||||||
|
(실제 Parent 조작)
|
||||||
|
|
||||||
|
## M7 — Modifier
|
||||||
|
|
||||||
|
- [ ] flatten-before-dispatch, immutable `table.clone` 체이닝
|
||||||
|
- [ ] `Modifier.Merge`(가칭)
|
||||||
|
- [ ] `State<Modifier>` 조합 타입 차단 확인(`modifier-plan.md` 7번, UB 확정)
|
||||||
|
|
||||||
|
## M8 — Ref
|
||||||
|
|
||||||
|
- [ ] `CreatedRef` 메커니즘(숫자 슬롯 참가자)
|
||||||
|
- [ ] `LifetimeHandle` 인터페이스 + quad-roblox 실제 구현(Instance 생존 확인)
|
||||||
|
- [ ] `PerInstanceState` 인터페이스 + quad-roblox 실제 구현(weak-keyed table)
|
||||||
|
|
||||||
|
## M9 — 컴포넌트 합성 레이어
|
||||||
|
|
||||||
|
- [ ] 플레인 함수 컴포넌트 관례 문서화/예제
|
||||||
|
- [ ] `props.Modifier`/`props.Ref` 전달 관례를 정식 컴포넌트로 검증(M0
|
||||||
|
스파이크를 정식화)
|
||||||
|
|
||||||
|
## M10 — Event / Attribute / Tag
|
||||||
|
|
||||||
|
- [ ] `Handlers/Event.luau`(`ReflectionService` 기반 자동 판별)
|
||||||
|
- [ ] `Handlers/Attribute.luau`
|
||||||
|
- [ ] `Handlers/Tag.luau`(`CollectionService`)
|
||||||
|
|
||||||
|
## M11 — Tween
|
||||||
|
|
||||||
|
- [ ] `research/tween-plan.md` 남은 옵션 이름 확정(구조는 이미 확정)
|
||||||
|
- [ ] `Handlers/Tween.luau`(높은 우선순위 store-bind 핸들러, 기본 오버라이드
|
||||||
|
Cancel)
|
||||||
|
|
||||||
|
## 특정 마일스톤에 안 묶이고 병행 가능
|
||||||
|
|
||||||
|
- [ ] 용어 정리 스윕 — `State`/`DI`/`PerInstanceState`/`Slot` 등
|
||||||
|
(`.claude/question.md` 1번), 최종 이름 확정되는 대로 아무 시점에나
|
||||||
|
- [ ] 각 마일스톤 완료 시 `.claude/qa-request/`/`.claude/archive/`에 기록,
|
||||||
|
필요하면 `CLAUDE.md` "최근 세션 요약"도 갱신
|
||||||
|
|
||||||
|
## 백로그 (스코프 밖 — 필요성이 실제로 드러나면 그때 설계)
|
||||||
|
|
||||||
|
- [ ] `research/existing-instance-bind-plan.md` — Modifier 정적 flatten과
|
||||||
|
긴장 관계 있음, 재검토 시 그 문서부터 다시 볼 것
|
||||||
|
- [ ] 범용 렌더 디버깅 도구로서의 quad-mock(Tween mock 등 동적 동작 포함,
|
||||||
|
M1의 quad-base 테스트용 mock과는 별개)
|
||||||
|
- [ ] v1 마이그레이션 가이드 + `objectListClass.__newIndex` 오타 기능 재현 테스트
|
||||||
|
- [ ] Slot 형제 순서 보장(다중 백엔드 관점) — Roblox만이면 급하지 않음
|
||||||
Loading…
Reference in a new issue