architecture.md |
quad-v2 전체 아키텍처 확정 사항 요약(제일 먼저 볼 문서). [2026-08-12 세션 신설, 같은 날 후속 세션에서 강화] "코드 스타일 — Luau 문법 관례" 절 신설 — if-then-else가 공식 Luau 문법임을 명문화(환각/오타로 오인해 and/or로 되돌리는 회귀 방지), A and B or C 삼항 관용구는 항상-truthy 예외도 없이 전면 금지로 강화(bind-system-plan.md의 retractUnder falsy-값 버그가 실사례). const 바인딩은 공식 문법이나 툴링 미성숙으로 지금은 채택 보류 |
typing-limits.md |
[2026-08-13 열세 번째 세션 신설] Luau 타입 시스템이 quad 설계에 대해 못 해주는 것을 한 군데 모은 확정 문서 — 여러 base/ 문서에 캐비엇으로 흩어져 있던 걸 통합. 대전제는 "Luau의 한계를 우회하려고 타입/API를 비틀지 않는다"(비틀면 나중에 Luau가 고쳐줘도 자동 수혜를 못 받고 되돌리는 마이그레이션이 생김). 1번 항목이 가장 큼 — 재귀 제네릭이 다른 타입 인자로 자기를 반환하면(Compute<U>(self: State<T>,...) -> State<U>) 타입 안전성이 에러 없이 조용히 사라짐(구 question.md 0-Y, 스파이크 44개로 확정). 대응은 두 개: (a) 타입 선언을 "데이터부/메소드부"로 쪼개 콜백 파라미터 추론을 살리고, (b) 파생 State를 만드는 자리마다 결과 타입을 명시 주석으로 바인딩(그 한 줄만 검증 안 되고 다운스트림 전체는 정상 체크됨). Luau RFC relax-recursive-type-restriction이 Promise<T>.andThen으로 예시 든 바로 그 패턴이라 지금 선언 그대로 두면 Luau 쪽 수정만으로 코드 변경 없이 풀림(추적: luau-lang/luau#2380). 그 외 Modifier Overridden 서브타입/Attribute 제네릭 키 narrowing/nilable default 오버로드/store.key type function 한계도 여기 통합, 7번에 새 타입·API 설계 시 체크리스트. 실측 근거는 audit/type-recursion-issue/ |
lifecycle-pattern.md |
rbvm의 Connected+GC 관용구를 quad-v2가 채택하는 방식. [2026-08-14 다섯 번째 세션, 시그니처 정정] bindLifetime(inst,value)/unbindLifetime(value)/canExecute(value) — 뒤의 둘은 inst를 안 받음(bindLifetime이 바인딩 시점에 gcconn 참조를 value 쪽 Relate로 복사해두므로 value 하나로 생존을 물을 수 있고, 실제 호출부인 State 전파 루프엔 애초에 inst가 없음). .Subscribed는 전역 :Subscribe() 전용 필드로 분리(bindLifetime은 읽지도 쓰지도 않음), 별도 canBound는 폐기되어 canExecute 하나로 통합, gcconn/gchold는 lazy가 아니라 Instance 생성 시점에 만들고 클로저가 gchold와 inst를 둘 다 캡처(userdata 포인터 동일성 = inst-키 Relate 전체의 전제). 옛 2-인자 모델은 archive/canexecute-inst-arg-reversed.md |
store-plan.md |
[2026-08-14 신설 — bind-system-plan.md 3단계 분할 + 구 store-semantics.md 흡수] Store = 이름 붙은 Source 모음, 그 이상 아님 — Store 부작용 허용이 기본 디자인(국소적 vs 경계를 넘는 부작용), defaults는 선택적 초기값 템플릿(원본을 나중에 mutate해도 UB 아님)이고 eager 생성과 lazy 생성이 둘 다 필요(Luau 타입은 런타임에 강제 안 되므로), table.clone 기반 eager 생성 스케치, store.key(dot-access)가 1급 경로이고 store "key" 문자열 커링은 동적 키용 미타입 폴백, 레코드 필드 타이핑은 Luau type function으로 해결 확인, store.key = value 폐기 → store.key:Set(value)(타입 대칭성+lazy 정직성), "Store가 Store를 저장 가능한가"는 그런 경우를 안 만듦으로 확정(State<State<T>>와는 다른 축) |
source-state-plan.md |
[2026-08-14 신설 — bind-system-plan.md 3단계 분할 + 구 store-semantics.md 흡수] 반응형 코어: Source⊇State 구조적 서브타입(RefSource 폐기, 단방향 의존으로 Luau 솔버 회피 — 스파이크 08 통과), push-invalidate/pull-recompute 전파 모델과 "관측해야 실체화된다" 전역 원칙, State 체인 플래튼 기각(캐싱이 State의 존재 이유), :With도 매번 새 노드(clone 계열인 Tag/Modifier와 혼동 주의), :Compute의 lazy 핸들 계약(:Get() 누락이 반복되는 실수)·trailing args sugar·fn(self, previous?, ...deps) 순서·previous, :Apply, :Emit()(Source 원천 전용 하드 경계)과 Store<T>/Source<T>의 T가 Modifier일 수 없는 따름정리, state:Observer(fn), :Subscribe()/:Unsubscribe(), 이중 바인딩 금지 게이트(canExecute 하나로 통합), PA님 코드 교차검증 |
dispatch-core-plan.md |
[2026-08-13 열네 번째 세션 신설 — bind-system-plan.md 2단계 분할 + 0-A/0-Z 반영] 디스패치 코어: 핸들러 계약(isHandlable/priority/process가 retract 클로저를 반환) / 하강 diff 재디스패치(래핑 핸들러의 retractFrom 선행 호출 폐기, Dispatch.process가 슬롯의 handler를 먼저 비교해 — 같으면 그 자리 클로저에 새 값을 넘기고 재process, 다르면 그 자리부터 전량 철거) / chains 인덱스 체인과 3-인자 Dispatch.retractFrom(inst,k,index)(힌트 인자 소멸 — 값 전달 경로가 (A) 분기 하나로 통일) / None 센티널 / Handler 작성 체크리스트 8개 / Length·Offset 형제 순서 보장 / "store 바인드는 래핑" 결론. 새 결정 둘: HANDLER_PRIORITY_FALLBACK(base 제공 핸들러의 기본 밴드 — 백엔드가 평범한 우선순위로 덮어쓰면 언제나 이김), "base가 소유하는 핸들러와 주입되는 엔진 op"(부기가 엔진 지식을 요구하지 않으면 알고리즘은 base, 마지막 한 줄만 주입 — addTag/removeTag/setAttribute, [2026-08-14 열 번째 세션] 같은 패턴을 Dispatch 밖의 dispose(value)/disposeInst에도 재사용). 옛 힌트 모델은 archive/dispatch-hintvalue-model-reversed.md |
bind-system-plan.md |
[2026-08-14, 3단계 분할로 203줄까지 축소 — 지금은 "인스턴스 생성/이벤트 네이밍 인체공학 + 분할 색인" 문서] 반응형 코어는 source-state-plan.md, Store는 store-plan.md, 디스패치 코어는 dispatch-core-plan.md로 나갔음. 아래 이력은 분할 전 이 파일이 담고 있던 결정들의 기록(현행 소스는 각 분할 문서). pluggable key/value 핸들러 레지스트리 — process/retract 디스패치 모델, Ref, Store/State/Source 온톨로지 + 인체공학 질문 전부 확정. 디스패치 엔진은 quad-base가 인터페이스로 소유(2026-08-04 5차 라운드). [2026-08-11 세션, 여섯 번째] Dispatch.setLength/setOffsetSource의 owner 키가 물리 Instance로 한정될 필요 없음을 명시(Slot-in-Slot 재귀의 근거) — 같은 절 recompute의 off-by-one 버그 발견·수정(offset이 자기 자신을 포함해 누적되던 것), 재진입 방지 가드는 검토 후 기각(Source⊇State 단방향 원칙과 같은 카테고리의 UB로 명명, 각 Slot이 독립 bk를 가져 nesting만으로는 재진입 경로 자체가 없음을 확인). [2026-08-12 열한 번째 세션, 전면 정정] "핸들러 타입이 안 바뀌면 retract 없이 process가 diff"는 틀렸음 — retract는 store 재발행마다(핸들러 타입 무관) 항상 불림, v는 대체 값 자체일 수 있어 nil로 가정 금지. Tag/Ref/Slot/Attribute 전부 이 오류로 설계돼 있었음이 드러나 한 세션에 전부 정정(archive/retract-always-fires-reversed.md). [2026-08-12 세션 후속] retractUnder의 A and B or C 삼항 관용구 버그(v가 false일 때 nil로 새던 것)를 if-then-else로 수정한 게 계기가 되어 and/or 삼항 전면 금지 규칙으로 발전(architecture.md "코드 스타일" 절). [2026-08-12 열일곱 번째 세션] 우선순위 동률/매치 실패 처리(HANDLER_PRIORITY_* 상수+디버그 동률 감지, 매치실패는 즉시 error) 확정, store.key 레코드 필드 타이핑이 Luau type function으로 가능함을 스케치로 확인(pre-implementation-audit.md 1-3/1-4/1-10 해소). [2026-08-12 스무 번째 세션] Ref 사용 관례 명문화 — React useRef급 스코프 감각(만든 컴포넌트 자신이 쓰거나 자식에게 넘기는 용도, 경계 밖 반출·전역 장기 보관은 비권장). [2026-08-12 스물한 번째 세션] :With가 Tag/Modifier의 : clone 체이닝과 겉보기엔 같은 문법이지만 실제로는 정반대(clone 아니라 매번 새 State 노드)라는 혼동 경고 추가, Compute가 -ed(Computed)가 아닌 이유 절 신설(quad 자기 관례상 Tag.Added/Modifier.Overridden이 이미 "-ed = clone 후 즉시 확정된 값"을 선점해 lazy한 State에 재사용하면 충돌). [2026-08-13 세션, 두 번째] State<State<T>>(store가 emit하는 값 자체가 또 State/Source)가 같은 (inst,k)에 같은 핸들러를 중복 push시켜 retractUnder의 첫-매치 cutoff가 안쪽 자신을 잘못 retract하는 실제 체인 파손 버그로 확인됨(손 트레이싱, luau-test/04가 no-op retract 스텁 때문에 이 증상을 못 잡던 사각지대였음도 같이 발견) — Dispatch.process에 중복 핸들러 즉시 error 가드 추가, "동일한 재귀적 디스패치로 처리 가능"이라던 낙관적 서술과 "Store가 Store를 저장 가능한가" 절도 정정. [2026-08-13 세션, 네 번째] 사각지대 손 트레이싱 라운드에서 isHandlable 필드를 선택적으로 허용(생략하면 스캔에 안 걸림)하고, 그런 "체크포인트" 핸들러를 명시적으로 체인에 꽂는 Dispatch.processAs/Dispatch.retractSelfAndUnder(target 자신 포함 철거) 신설 — attribute-plan.md의 그룹/직접쓰기 이름 소유권 충돌을 별도 레지스트리 없이 기존 재진입 가드로 흡수하는 데 씀. [2026-08-13 세션, 다섯 번째, 전면 재설계 — 위 processAs/retractSelfAndUnder 대체] chains를 핸들러 객체 identity가 아니라 재귀 깊이 인덱스로 추적하도록 재설계 — Dispatch.process(inst,k,v,index)가 핸들러 호출 전에 그 인덱스 점유 여부를 체크(핸들러 부작용 낭비 없음), process는 이제 retract 필드 대신 자기 retract 클로저((hintValue)->())를 반환. 같은 키 재귀는 index+1, 다른 키 위임은 항상 1부터 — 이걸로 State<State<T>>가 UB에서 정상 지원 대상으로 재정정됨(각 재귀 단계가 다른 슬롯을 쓰니 identity 충돌 자체가 없어짐), retractUnder/retractSelfAndUnder도 Dispatch.retractFrom(inst,k,index,v) 하나로 통합(자기 포함/미만은 호출자가 넘기는 인덱스로 표현)되며 체크포인트 패턴 자체가 불필요해짐(archive/checkpoint-handler-pattern-reversed.md). 계기: AttributeGroupHandler 소유권 버그를 체크포인트로 고치다, 그 근본 원인(identity 기반 추적)을 되짚은 사용자 지적. [2026-08-13 감사] 위 재설계 의사코드에서 실제 버그 셋 발견·수정 — (1) chains:SetStrong이 handler.process 뒤에 있어 최초 마운트에서 하위 위임 retractor가 통째로 유실되던 것(재귀가 자기 테이블을 만들었다 바깥이 덮어씀), (2) Ref retractor가 spurious 재발행에서도 relate를 지워 dedup이 무력화되던 것, (3) Dispatch.drive의 진입 인덱스(1) 미명시. 덧붙여 retractor 안에서는 같은 키에 대한 retractFrom도 process와 똑같이 금지(진행 중인 루프가 #list를 이미 캡처)임을 명문화 **[2026-08-13 열네 번째 세션] 2단계 분할 + 모델 교체 — 디스패치 코어 전체가 dispatch-core-plan.md로 나갔고(이 문서엔 반응형 코어와 인체공학만 남음), 나가면서 하강 diff로 재작성됨. 따라서 위 5차 세션 서술 중 "Dispatch.process가 인덱스 점유 여부를 먼저 체크"와 "retractFrom(inst,k,index,v) 4-인자"는 더 이상 현행이 아님(점유 체크 폐지 → 핸들러 비교, 힌트 인자 소멸 → 3-인자) — 현행은 dispatch-core-plan.md |
module-lifecycle-plan.md |
프로바이더 패턴, bind/store 구현 책임 분리 — 확정 |
slot-plan.md |
뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정. [2026-08-09 세 번째 세션] Add/Remove/Extract/Clear/Move/Swap CRUD(복잡도 표기 포함), isMounted 이중 추적 분리, 요소 타입 제약(nil/None/핸들러 계층 값 금지, Slot<T>() 제네릭), 키 기반 동적 컬렉션 재조정(Slot:List(data, updateFn, keyFn?))까지 전부 확정 통합, base/roblox 경계에 reposition 훅 추가. [2026-08-09 열한 번째 세션, 중간검토] CRUD 식별 기준을 element 레퍼런스에서 인덱스 기준으로 재정정(Remove(index)/Extract(index, newElement?)/Move(oldIndex, newIndex)), ExtractAll/Get/IndexOf 신설. [2026-08-11 세션] `updateFn(item, index: number, offset: Source, prev: T?, userdata: UD?): (T |
modifier-plan.md |
Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정. [2026-08-07 다섯 번째 세션 추가] :Apply(factory) 팩토리 체이닝, Overridden(구 Merge→Override, 2026-08-08 세션에서 이름까지 확정) 값 결합+성능 기준, :Peek/isState 필드 읽기까지 전부 확정(Peek/isState는 이름만 용어 정리 라운드까지 잠정). [2026-08-12 열일곱 번째 세션] table.clone이 메타테이블을 참조로 공유한다는 핵심 전제(M7 "클래스별 코드 없이 제네릭 __index 하나로 충분" 설계의 근거)가 실제 Luau 동작으로 확인됨(pre-implementation-audit.md 1-11 해소). Property에 Attribute식 이름 소유권 레지스트리를 적용하는 안은 검토 후 기각(엔진이 정한 유한 프로퍼티 이름 집합은 전용 키를 못 만들어 소유권 판정 자체가 성립 안 함 — Property가 override 우선순위를 쓰는 이유) |
purity-and-effects-plan.md |
컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 |
component-composition-plan.md |
컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, Source가 State를 구조적으로 만족 — modifier/Ref 컴포넌트 경계 통과까지 전부 확정, 남은 건 API 이름뿐. [2026-08-07 정리] 폐기된 StoreSource 프록시 설계로의 역전 이력은 본문에서 빼고 archive/store-source-proxy-reversed.md 포인터로 압축 |
blocker-plan.md |
[2026-08-07 신설] Blocker — 여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게, State 마일스톤(M3)과 함께 개발. 메커니즘+이름 확정 |
effect-plan.md |
[2026-08-07 신설, 여섯 번째 세션에 확정] Effect(fn, state?) — state 없으면 설치 1회+leaf 사망 시 확정 정리, 있으면 내부적으로 state:Observer(...)를 조합해 재실행+cleanup 체이닝(React useEffect 동형). Observer와의 관계 해소 완료 |
ui-shorthand-plan.md |
[2026-08-07 research/에서 승격] UICorner/UIPadding/UIScale 인라인 편의 키 — 이름(v1 Corner/PaddingAll/Scale에서 Modifier 필드명과 안 겹치게 UI 프리픽스로 확정)·메커니즘(Handler)·패키지 배치(quad-roblox 코어)·store-bind 가능성까지 전부 확정. 이미지 라운드 트릭(RoundSize)은 드롭 — archive/ui-shorthand-roundsize-dropped.md 참고. v=nil이면 process 자신이 만든 자식 제거(retract 아님). [2026-08-14 세션] Tween 지원 추가 — 자식 프로퍼티를 직접 대입하지 않고 Dispatch.process(child, prop, ..., 1)로 위임하는 것으로 확정(프로세스 중 inst를 바꾸는 건 키를 바꾸는 것과 같은 층위라 UB 아님, dispatch-core-plan.md에 일반 규칙으로 명문화) — Tween 해석 코드가 PropertyHandler 하나에만 남는다는 불변식이 유지되고, 이 문서가 새로 정할 건 스칼라→프로퍼티 wrap을 Tween<T>.Value에만 적용되도록 들어올리는 헬퍼 하나뿐. 옛 "트윈까지 지원할 필요 없음" 서술은 역전됨(그때는 Tween이 독립 Dispatch 핸들러였음). ROADMAP M10에 빠져 있던 체크리스트 항목도 이 세션에 보강 |
tag-plan.md |
[2026-08-08 세 번째 세션 재설계, 2026-08-12 열한 번째 세션 메커니즘 정정] Tag(...) — array-part 값 객체, Modifier와 같은 immutable clone 체이닝(:Added/:Removed/:Contains/:Apply/Merged), CollectionService 글루만 quad-roblox. retract가 이전 Tag가 걸었던 이름을 이름별 참조 카운트 맵에서 빼고(다른 위치가 겹쳐 쓰면 실제 RemoveTag는 skip), process가 새 Tag의 이름을 등록 — 여러 위치가 같은 이름을 겹쳐 가져도(웹 className류 합집합) 안전. 구 해시 파트 boolean 모델은 archive/tag-hash-key-model-reversed.md, 구 assert(v==nil) 메커니즘은 archive/retract-always-fires-reversed.md. [2026-08-12 열다섯 번째 세션] Added/Removed가 vararg가 아니라 `string |
attribute-plan.md |
[2026-08-07 여덟 번째 세션 신설] 단일 키 [AttributeKey<T> "Name"](구 Attribute<T>) — SetAttribute(name, nil)이 네이티브 지우기라 None 센티널과 가장 깔끔하게 맞아떨어짐. [2026-08-11 아홉 번째 세션] 여러 Store를 한 번에 attribute로 묶는 그룹 Attribute(...) 프리미티브 신설(Tag와 동형 array-part 값 객체, Merged로 헤테로지니어스 Store 합성), 이름 충돌 방지로 단일 키를 AttributeKey로 리네임(잠정). [같은 세션 후속] AttributeKey(name)이 이름별 weak 캐시로 동등성 보장하도록 확정되며, 그룹 Handler는 자기 완결형 재구현 대신 메모이즈된 키로 기존 단일 키 경로에 재귀 위임하는 걸로 개정(중복 구현 제거). [2026-08-12 열 번째 세션] 그룹/직접 쓰기가 같은 이름을 동시에 관리하는 충돌을 막기 위해 그룹은 공개 캐시 대신 rawNew(name) 전용 키+소유권 Relate로 전환. [열한 번째 세션] retract가 store 재발행마다 항상 불린다는 정정에 맞춰 AttributeKeyHandler.retract를 손봄(이 시점엔 v==nil 가드 버전 — 아래 열여섯 번째 세션에서 최종 재정정됨), 그룹의 "남아있는 이름" 위임도 매번 retractUnder를 먼저 부르도록 정정(체인 누수 방지). [2026-08-12 열여섯 번째 세션, 최종 재정정] retract는 완전 no-op으로 굳어짐(SetAttribute는 오직 process(inst,k,nil)에서만) — Attribute는 명시적 None/nil로만 지워지고, 그룹 diff나 컴포넌트 언마운트로 이름이 조용히 사라져도 값은 자동으로 안 지워짐(Ref의 "Destroy 무관, 정리는 명시적으로" 철학과 통일), 단 사라진 이름의 구독은 끊어 자원 누수는 막음 — 위 "v==nil 가드" 버전은 이걸로 폐기. [2026-08-13 세션, 전면 재정정] rawNew+owners 수동 레지스트리 방식이 "그룹이 이름을 놓았다 다시 포함하면 자기 자신과 충돌"하는 실제 버그로 확인됨 — AttributeGroupKeyHandler라는 isHandlable 없는 순수 체크포인트 핸들러를 Dispatch.processAs로 명시 push하고 Dispatch.retractSelfAndUnder로 통째 철거하는 방식으로 전면 재설계, 소유권 충돌 감지도 별도 레지스트리 없이 기존 재진입 가드가 대신 잡아줌(bind-system-plan.md 참고). AttributeKeyHandler는 다시 완전 무상태로 단순화됨. [2026-08-13 세션, 다섯 번째, 전면 재설계 — 체크포인트조차 불필요해짐] Dispatch가 인덱스 기반으로 재설계되며 AttributeGroupKeyHandler/processAs/retractSelfAndUnder를 전부 걷어냄 — 그룹이 그냥 공개 AttributeKey(name)으로 항상 인덱스 1부터 Dispatch.process/retractFrom을 직접 부르면 끝(점유 체크 자체가 소유권 충돌 감지), groupState Relate도 필요 없어짐(반환 클로저가 이름 집합을 직접 캡처) — 중간 버전은 archive/checkpoint-handler-pattern-reversed.md. [2026-08-13 감사, 정정] 그런데 그 의사코드가 process 안에서 이름마다 retractFrom(...,1,...)을 먼저 부르고 있어 인덱스 1이 무조건 비워지는 바람에 점유 체크가 전혀 작동하지 않았음(그룹↔그룹 사이에서 조용한 last-write-wins가 그대로 남아 있었음) — process는 Dispatch.process만 부르고 철거는 반환 클로저가 자기가 등록한 이름 전부에 대해 하도록 정정. 그룹 Handler 시그니처가 계약과 안 맞던 것(process(inst,index,v) 3-인자)도 같이 수정 [2026-08-13 열네 번째 세션, 0-Z 확정] 그룹이 자기 전용 키(비공개 GetKey)로 위임하고 이름 소유권은 AttributeKeyHandler의 이름 claim(nameClaims Relate, 충돌 시 즉시 error)이 판정 — 하강 diff에선 두 그룹이 똑같이 StoreBind로 보여 점유 체크가 성립하지 않기 때문. 후보 (a)(그룹 안 claimant Relate)는 그룹↔직접 쓰기를 못 잡아 기각. 같은 세션에 패키지 재배치(값·알고리즘·단일 키 전부 quad-base, 백엔드는 setAttribute(inst,name,v)만 주입, 엔진 고유 타입 패밀리만 백엔드) |
onchange-plan.md |
[2026-08-10 세션 신설] OnChange(name) — GetPropertyChangedSignal 바인딩 전용 DI 키, Attribute와 달리 제네릭 타입 파라미터 없음(콜백 타입은 인라인 명시, 이벤트 바인딩과 같은 급 트레이드오프). 전부 quad-roblox(Handlers/OnChange.luau), State<function>은 기존 이벤트 store-bind 메커니즘 재사용. [2026-08-11 아홉 번째 세션 후속] AttributeKey와 동일한 이름별 weak 캐시로 OnChange(a) == OnChange(a) 동등성 보장 |
relate-plan.md |
[2026-08-08 신설] Relate — inst를 weak 키로 하는 범용 릴레이션 프리미티브(SetWeak/GetWeak/SetStrong/GetStrong, 비싱글톤 생성자). 구 base.perInstanceState(inst) placeholder를 대체·정식 승격, lifecycle-pattern.md의 bindLifetime/canExecute가 그 위에 얹힘. [2026-08-12 열세/열네 번째 세션] 서로 다른 두 Relate가 서로의 키를 상대방 값으로 강하게 붙잡는 상호 순환 패턴 경고 신설 — Luau에 ephemeron 테이블이 없어(공식 확인, luau.org/compatibility) 이런 순환은 실제로 GC가 안 됨, Slot의 kSlotMap/slotOwner가 실제 사례이자 수정 사례 |
ref-plan.md |
[2026-08-13 아홉 번째 세션, bind-system-plan.md에서 분리] Ref/PreRef/PostRef — 지연 없는 확정 값 박스. 용도 재정의(leaf 노드를 담는 용도로도, leaf 노드에 바인딩하는 용도로도), .Value+:Set/:Callback/:Wait(전부 self 반환), Ref의 retract가 TagHandler와 같은 Relate diff 패턴이라는 것, 이중 바인딩 금지(canBound), PreRef 호이스팅 pre-pass와 1회용 _fired 가드. 분리는 순수 이동 — 결정은 하나도 안 바뀜 [2026-08-13 열네 번째 세션] 하강 diff 반영(0-Z 배너 해소) — "이전 클로저가 언바인딩, 다음 process가 바인딩" 두 단계는 그대로이고 그걸 일으키는 주체만 Dispatch.process의 핸들러 선비교로 바뀜 [2026-08-14 아홉 번째 세션] PostRef 확정·편입 — PreRef의 거울상(같은 pre-pass가 수집만 하고 두 패스가 전부 끝난 뒤 fire, ProcessedPostRef 센티널+전담 Handler까지 완전 대칭). 보장 범위는 "자기 서브트리 완성"이고 자기가 부모에 붙는 것보다는 여전히 먼저임에 주의. 계열 안 fire 순서는 배열 index 순서 보장 유지(같은 세션에 미보장으로 뒤집었다 철회 — archive/preref-order-unguaranteed-withdrawn.md) |
event-plan.md |
[2026-08-13 아홉 번째 세션, bind-system-plan.md에서 분리 — 사용자가 직접 지목] 이벤트 바인딩 — 핸들러가 self(Instance)를 안 받는다는 확정(Ref가 이미 커버, 이중 쓰기 경로 방지), 이벤트도 store-bind 가능하며 false를 넣으면 disconnect. 이벤트 네이밍 관례는 인스턴스 생성과 한 절에 섞여 있어 bind-system-plan.md에 남음, GetPropertyChangedSignal은 onchange-plan.md. 분리는 순수 이동 |
brand-plan.md |
[2026-08-13 아홉 번째 세션, bind-system-plan.md에서 분리] Brand — 런타임 nominal 타입 판별 통합 메커니즘(Brand.set/Brand.get), isState를 branded 타입 전부로 일반화(isPostRef 포함, 2026-08-14 아홉 번째 세션). 동작/구현은 확정, 이름 Brand 자체만 용어 정리 대기(question.md 1번). 분리는 순수 이동 |
tween-plan.md |
[2026-08-12 세션, research/에서 승격] 값-레벨 Tween<T> 래퍼(PropertyHandler가 소비, 구 특수 bind key 모델은 archive/tween-special-bind-key-reversed.md). 3-상태 릴레이션 슬롯({Tween,Value}|true|nil), T'=T|Tween<T> 타입 치환. 옵션 값 모양은 Info: TweenInfo? 우선+편의 필드 폴백, override는 Tween.Cancel(기본)/Tween.Finish 2값. Animate(info)는 Tween opts를 T|State<T>로 받아 :Apply로 꽂는 sugar. 자연완료 시 per-instance 북키핑은 정리 안 해도 됨으로 확정(목표값 도달 상태라 부작용 없음, Completed 이벤트 구독 장치는 오버엔지니어링으로 판단). initValue는 사용자가 직접 처리(에이전트 범위 제외) |
fallback-plan.md |
[2026-08-14 세션, research/에서 승격] Fallback/Traceback — 컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 순수 슈가(additional-primitives-plan.md의 "Error Boundary는 빈 자리 아님" 결론 위에 얹힘). Fallback은 pcall 기반(trace 없음), Traceback은 xpcall+debug.traceback 기반(trace 항상 있음) — 플래그 대신 별도 함수로 분리(Ref/PreRef와 같은 패턴). err: any(Lua error()가 임의 값을 던질 수 있음, error(msg) 기본 호출의 위치 접두 캐비엇 포함) 확정. 패키지는 quad-base, 이름 확정. 메커니즘 실측은 audit/fallback-xpcall-verification.md. 구현 우선순위는 형제 백로그(quad-mock/quad-debug/Operator)와 동급, 맨 뒤 |
lifecycle-hooks-plan.md |
[2026-08-14 아홉 번째 세션, research/에서 승격] 생명주기 훅 슈가 OnCreated/OnRendered/OnDestroyed — 각각 PreRef():Callback(fn)/PostRef():Callback(fn)/Effect(function() return fn end)를 반환하는 순수 팩토리 함수라 새 타입/Dispatch 개념이 전혀 안 생김(호출 즉시 평가돼 기존 인스턴스로 사라짐), 여러 개 나란히 등록도 자연 지원(단 같은 계열끼리의 순서는 미보장). 마지막 열린 항목이던 OnRendered는 사용자가 채택 확정 — 메커니즘은 PostRef(base/ref-plan.md), 원래 열어뒀던 (a)/(b)/(c) 중 (a). 캐비엇: OnRendered는 서브트리 완성은 보장하지만 이 인스턴스가 부모에 붙기 전에 불림(React componentDidMount와 다름) — 문서화 필수. 패키지 quad-base 확정. [2026-08-14 열 번째 세션] dispose() 범위(0-B)가 Slot+Instance로 좁혀지고 Observer/Effect는 제외되는 쪽으로 확정되며 OnDestroyed 이름 재검토 조건이 발동 없이 종결 — OnDestroyed가 최종 이름, 용어 대기열에서도 제외 |