quad/.claude/qa-request/pre-implementation-qa-round4-response.md
qwreey e80aa11c4f
qa: 구현 전 QA 4라운드 문항지 — base/ 전 문서 확정 주장 전수 문항화
사용자 요청("모든 확정 부분에 있어서 예가 되어야하는 질문들을 계속")으로
base/ 26개 문서를 서브에이전트 없이 한 맥락에서 의존성 순서로 읽으며,
확정으로 적힌 주장을 전부 "예가 나와야 정상인 단정문"으로 뽑았다.
결론만이 아니라 근거까지 문항에 넣었는데, 코퍼스에 "결론은 그대로인데
근거가 뒤집힌" 사례가 여럿 있어서 결론만 물으면 그런 걸 못 잡기 때문.

⚠️로 열려 있다고 적힌 항목은 "확정이 맞나"가 아니라 "아직 열려 있다는
인식이 맞나"를 묻는 문항으로 따로 표시했다.

정정은 하나도 안 했다 — 사용자 지시대로 기록만 하고 회신 대기 상태.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-20 00:33:49 +09:00

26 KiB

D-3 — retractor는 자기 자원만 정리

retractor는 자기 하위 위임까지 쫓아가 정리할 필요가 없다 — Dispatch.retractFrom항상 깊은 인덱스부터 얕은 쪽으로 정리하므로 이 클로저가 불릴 시점엔 자기 아래는 이미 정리된 뒤다. → 예/아니오 -> 방향이 그게 맞나? 방향을 더 서술해주길 바람. 실제 process 에선 retract 가 인덱스 1 부터 5, 6, ... 순으로 작동함. 그런데 달라질 때 5, 4, 3, 2 ... 순이 되는건 아니지? '깊은 인덱스' 라는게 무슨 의미?

D-8 — Quad.debug 플래그

동률 경고 print는 모듈 표면의 불리언 Quad.debug(기본 false)가 참일 때만 찍는다. Quad.debug는 새 공개 API 표면이라 module-lifecycle-plan.md에도 반영이 필요하고, 다중 인스턴스화 시 인스턴스별인지 전역인지는 그때 정한다. Dispatch.listHandlers()가 이 플래그와 무관하게 항상 호출 가능한지도 구현 시 정한다. → 예/아니오 -> listHandlers 는 항상 실행 가능. 유저가 필요하면 수행 시 목록들을 단순 반환해주고 출력하고 싶다면 출력하는 용도임.

D-10 — 두 패스 순회 계약

Dispatch.drive는 Lua 테이블의 우연한 순회 순서에 기대지 않고 명시적으로 두 패스(배열 파트 먼저, 해시 파트 나중)로 돈다. 이유는 (1) 다른 백엔드의 이식성, (2) 어차피 구분 비용이 드니 순서 고정이 거의 공짜. M0 스파이크에서 실제 Luau로 순회 동작을 검증할 항목이다. → 예/아니오 -> 루아우의 일반화된 반복 for 이 이를 그냥 지원함. 어떻게 실제로 이해했는지 확인 필요

D-32 — 재위임 핸들러의 (A) 분기 의무

재위임하는 핸들러는 (A) 분기에서도 반드시 다시 재위임해야 한다. 조건부로만 재위임하는 핸들러를 만들면 건너뛰는 자리에서 Dispatch.retractFrom(inst,k,index+1)을 직접 불러야 한다. → 예/아니오 -> 무슨말인지 확인 필요

D-56 — setLength의 생명주기 경로

setLength가 만드는 Observer는 :Subscribe()가 아니라 bindLifetime(ownerKey, observer)로 묶인다 — ownerKey가 죽을 때 같이 죽어야 하는 내부 배관이기 때문. setLength 자신은 recompute를 직접 부르지 않고 항상 gatedRecompute를 경유하며, Observer의 "등록 즉시 1회 실행"으로 촉발되는 최초 호출도 예외 없이 이 게이트를 통과한다. → 예/아니오 -> 맞음. 그런데 ownerKey 가 Slot일 수도 있음. 각 엔진의 bindLifetime 은 이를 잘 핸들링 해줘야함. 즉, Slot안에, 또는 바깥에 SetStrong 으로 gchold 비슷한걸 수행하면 됨.

D-58 — 마운트보다 offset 갱신이 먼저

rawAddself.Length:Set(newCount)(다운스트림 offset/LayoutOrder 갱신이 여기서 동기적으로 끝남) → element.Parent = target 순서로 호출해야 한다 — 안 그러면 Roblox의 실시간 UIListLayout reflow에서 한 프레임 순서가 깨진 채 노출된다. -> 추가적으로 보면, Length 설정도 마운트 전임. 액티베이션으로 length 가 결정되었을 때 그걸 넣어주고 나서 최종 마운트를 함.

D-60 — Slot.LengthSlot.Offset은 별개

Length는 Slot이 스스로 노출하는 출력값(지금 실제 마운트된 개수, "n개 검색됨" UI에 그대로 써도 됨)이고 Offset은 Dispatch가 등록받아 recompute가 채우는 입력값이다 — 서로 다른 두 Source<number>다. Offset은 마운트 전엔 nil이다. -> 마운트 전에는 0 이긴 함. 다만 list 의 관측으로 실체화된 값이 나오는게 offset 설정 이후라서 그 땐 0 이 아닐 수 있을 뿐

LP-1 — Connected는 계산 속성

Connected는 저장되는 bool이 아니라 "내가 아직 살아있게 하는 뒷받침 참조가 nil인지"를 확인하는 계산된 속성이고, 해제는 그 참조를 nil로 만드는 것뿐이다(자료구조를 즉시 재구성하지 않음). → 예/아니오 -> 무슨 말인지 모르겠음. Connected 는 단순히 RBXScriptConnect 안의 속성이고, Destroy 수행 시 모든 커넥션이 죽으니 자연스럽게 Connected 가 false 이 되는것 뿐임. nil로 참조를 만들 이유도 없음. gc 되어 아에 gcconn 이 없거나, 아직 gc 는 안 되었는데 Destroy 직후라 false 이거나 한것 뿐임.

LP-2 — Instance 파괴 관측 지점

Instance 파괴 관측 지점은 Destroying 하나로 통일하고 AncestryChanged나 폴링은 안 쓴다. 다만 실제로는 이 훅을 쓰는 지점이 예상보다 적을 가능성이 크다. -> 당장은 Effect 뿐임.

LP-4 — 엔진 레벨 보강

Roblox 엔진 자체가 Destroy 시 Tag/Attribute/실행 중인 Tween을 정리해주므로 라이브러리가 따로 처리할 필요가 없고, 커스텀 Destroy-time 처리가 필요한 사용자는 [Event "Destroying"]을 직접 바인드하면 된다. -> Effect 임. 그리고 그 슈거인 OnDestroyed 존재

BR-7 — duck-typing 기각 근거

duck-typing을 안 쓰는 이유는 (a) 우연히 비슷한 모양의 값에 false positive, (b) 일부 Roblox userdata는 정의 안 된 키 인덱싱 자체에서 에러를 던져 pcall로 감싸야 하거나 최악의 경우 엔진이 죽는 상황까지 생김 — 둘 다다. → 예/아니오 -> 더 설명좀 필요

M-2 — flatten의 판별 수단

flatten은 배열을 훑으며 isModifier(v)가 참인 항목만 필드를 뽑아 merge하고 나머지는 전혀 안 건드리고 배열 파트에 그대로 남긴다(그래서 None은 flatten을 그냥 통과한다). → 예/아니오 -> 이것도 이 이후 ProcessedModifier / ProcessedModifierHandler 를 만들면 될듯. Post/Pre Ref 와 유사히 처리 가능하다고 생각함.

M-5 — None은 raw 저장 계층에만 있는 실재 센티널

{ TextColor3 = None, mod }mod:TextColor3(None)도 둘 다 지원되고, Modifier setter/Overridden/인라인 props는 None을 그냥 평범한 raw 값으로 저장·교체할 뿐 특별 취급이 전혀 없다. 실제 "지우기"는 디스패치 단계의 NoneHandler가 담당한다. -> 예, 다만 mod:TextColor3(nil) mod 그룹에서 제거된걸 생성하게됨. None 이 오직 명시적 'unsetter' 임. 이미 그렇게 구현되겠지만, 문서화에 유의를 두지 않았다면 확인해두어야할 부분.

M-9 — Setter는 리터럴과 변환 함수 둘 다

:FontSize(value):FontSize(function(current) ... end) 둘 다 지원하고, Getter는 안 만든다(변환 함수 하나가 getter가 필요했던 유일한 케이스를 인라인으로 커버). old는 항상 "현재 저장된 그대로" 넘어간다(plain이면 raw 값, State면 State 핸들 그 자체). -> 사실 Peek 가 게터라고 봐도 되긴 함, 그래서 애매한 질문이였음.

BK-9 — HasBlocked 신설 안 함

IsBlocked/HasBlockedEmit 필드는 그대로 유지하고 Blocker 자신의 새 최상위 플래그(HasBlocked)는 신설하지 않는다OffWithoutEmit()이 각 gated state의 기존 HasBlockedEmit을 리셋해주는 것으로 충분하다. -> 있는게 어렵지 않다고 보긴 하나, 사용 케이스가 없었을 뿐임. 단순 백로깅 상태로 두어도 되나, 당장 개발에 필요 없음. 나중에 사용 필요 요구가 나오면 그 때 구현하여도 될 요소로 보임. 아마 HasBlockedState 로 하나가 신설될 가능성이 존재하지 않는다고 못 박기는 이름.

E-10 — ⚠️ 미해결 항목 확인: dedup 경로의 대칭

그 dedup 경로에서 retract가 아무것도 안 한 뒤 process 쪽도 정말 아무것도 안 하는지 대칭이 실제로 성립하는지는 아직 확인 안 된 항목이고, 특히 EffectHandle의 내부 Observer cascade가 dedup 분기 안에 제대로 들어가 있는지는 별도 확인 대상이다(M3 착수 전). -> 확인해봐야한다 생각함. 이로 인해 relate 로 effect 핸들러 쪽에서 old 값을 직접 들고 있어야 하고 dedup 이면 retract 에서 old 를 안 지워주고 process 로 조회해보고 같으면 deup 되어야하는듯.

E-11 — :Subscribe()한 핸들의 :Unsubscribe() 의미 확장

Observer의 :Unsubscribe()는 "미래 재실행만 끊는다"로 충분하지만, Effect의 계약은 "생애주기가 끝나는 시점에 마지막 cleanup이 정확히 1회"이므로 :Unsubscribe()도 "지금 끝났다"는 신호로 취급해 (1) 내부 Observer 구독을 끊고 (2) 직전 cleanup을 정확히 1회 호출하며 (3) 이후 leaf가 실제로 죽어도 중복 호출되지 않는다. → 예/아니오 -> 뭔가 애매함. 옵저버에선 leap 바인딩에 Unsubscribe 못 하는것 처럼, Effect 또한 리프 바인딩에 있어서는 Unsubscribe 안 먹어야 하는거 아님? State 오고 emit 당함 process 다시 나는데 문제가 안 되는거임? '(3) 이후 leaf가 실제로 죽어도 중복 호출되지 않는다.' 가 좀 이상해보임.

R-11 — 분기와 소진

type(v) == "thread"면 대기자로 보고 resume 후 [i] = nil로 소진, "function"이면 콜백으로 보고 호출만 하고 소진 안 함, nil이면 빈 슬롯이라 스킵한다. 새 등록은 table.insert가 아니라 빈 슬롯을 선형 탐색해 재사용한다. -> 아니요. table.insert 자체가 가장 처음 nil 이 등장하는 인덱스에 넣어주기에 table.insert 가 맞음. 단순 for 문에서는 해시 슬롯이든 어레이 슬롯이든 상관 없이 전부 for 가능해서, 여기선 callback 들 순서가 큰 상관 없어서 중간에 nil이 허용됨. 그리고 그 중간중간에 table.insert 가 잘 넣어주는게 맞음. None 으로 바꾸면 무한정 불어나지만, nil이면 그렇지 않음.

SL-4 — 핸들러 계층 값 금지의 근거

Dispatch/Leaf.luau가 처리하는 leaf 케이스는 그 컴포넌트가 지금 만들고 있는 Instance 자기 자신을 가리키는 self-ref 캡처inst가 고정돼야 의미가 성립하는데, Slot은 이미 존재하는 부모에 나중에 독립적으로 붙는 동적 리스트라 그 전제 자체가 없다 — Slot 안의 Ref가 무엇을 가리켜야 하는지 정의가 안 된다. 대체 경로(slot:Add(Frame { Ref = myRef }), 여기서 FrameRef라는 named 파라미터를 받는 컴포넌트 함수)가 있어 능력 손실도 없다. -> 맞고, 틀린건 없는데 Frame {} 로 두면 리프 노드로 보일 가능성이 있어보임. 실 문서상 그렇게 있다면 MyComponent 정도로 바꿔주는게 혼선의 여지가 없음.

SL-5 — isMounted 이중 추적 분리

Slot 컨테이너 자신은 self._mounted 필드 하나로, 개별 element는 전역 멤버십 (elementOwner)으로 추적한다. self._mounted의 트리거 시점은 Instance Parent 대입 완료가 아니라 Dispatch.process가 이 Slot에 대해 실제로 호출된 순간이다 — 다른 모든 "마운트됨" 판정이 dispatch-process 시점 기준이라 여기만 post-effect 기준이면 일관성이 깨진다. -> 더 풀어 서술해주길 바람. 판단 보류

SL-40, SL-43 SL-45

slot 에서 키가 사라지면 nil 로 updateFn 호출되는거 맞지? 근데 데이터 자체가 nil 일수도 있다는 생각. Detach 와 유사한 'KeyGone' 등의 상태지정 enum/싱글톤을 제공해버리고, T|KeyGone 하는게 안전해보인다는 생각. 'updateFn이 새 값을 반환하면 밀려난 prev언마운트만' 는 이상한듯. 새 값으로 밀려난 prev 는 dispose 되는게 맞음. updateFn 은 직접 destroy 를 호출 못함(reconcile 중 빼는게 안 되니까) 그래서 지울 방법이 존재하지 않고, 지워주는게 맞다고 봄. Detach 홀드 중 키 소멸은 KeyGone으로 처리되면 해결될듯. updateFn 은 위에서 if v == KeyGone then ... 처리하는게 있는게 나아보임. 소멸 루프가 keyIndex를 순회하는 것 - 또한, 단순히 KeyGone 을 주고, userdata 를 지울지 말지는 유저가 결정하게 위임해버리는게 가장 깔끔하다고 보이는 부분임. 이것으로 SL-45 도 닫아짐.

SL-48 — Destroy 이후가 공짜로 해결되는 이유

inst가 Destroy되면 gcconn이 죽어 canExecute가 거짓이 되고, gcholdRelate(inst) 아래 있어 그 안에 붙잡힌 Observer/클로저(mounted/userdata/ keyIndex 포함)가 전부 GC 대상이 된다 — 명시적으로 구독을 끊는 새 코드가 필요 없다. -> 더 나아가 slot in slot 에서도 유효한가 생각해보아야함. 아마 그런것으로 알고있음. 피지컬 홀더랑 오너가 다르거든.

SL-51 — :Single:List 위의 순수 sugar

:Single:List를 0/1개짜리 배열로 감싸는 sugar이고, key를 고정값으로 두는 게 핵심이다(값 자체를 key로 쓰면 매번 다른 item 취급돼 파괴+재생성이 강제됨). index를 안 넘기는 이유는 형제가 자기 하나뿐이라 항상 상수라서다. -> 주의할 점이 보임. state -> slot {frame} 형태가 될 때 이전 state 에서 변경으로 다른게 와도, slot 이 이전 frame 을 destroy 해버리면 안 됨. 뽑는게 안되면 안된다는건데, list 슈거에서 Detach 가 사용중인지 확인이 필요해보임. 또, Slot 안에 State 가 오면 이를 Slot으로 감싸주는게 유효할텐데, 거기서 안전한지도 봐야함(SL-66 에서 사용되는걸로 보이긴 하나, 안전 유무는 별도의 문제로 보임)

SL-58 — 배치 밖 단독 재마운트의 엣지 케이스

state<Slot> 값이 steady state에서 교체될 때는 부모 Blocker가 이미 꺼져 있어 부모의 gatedRecompute가 아직 flush 안 끝난 slot.Length로 한 번 계산할 수 있지만, flush가 끝나면 자기 교정된다 — 크래시도 영구 오류도 아닌 한 프레임짜리 낭비라 손대지 않기로 했다. -> offset 을 먼저 설정해 주어, 계산된 오프셋을 받은 다음 리스트의 액티베이션 이후 Length 를 확정된걸 setLength 하고 나서 마운팅 처리를 하는데, 이 경우 length 가 여러번 계산되어 낭비가 나진 않는거로 알고있음. 단순 위치계산 루프가 한번 돌지만, offset 이 전부 같아 set 안 일어나고 가벼운거로 아는데, 아님?

SL-59 — 재귀적 Clear() 금지

죽는 서브트리 내부에서 요소 수만큼 shift+recompute가 반복되므로, 순수 파괴 walk만 하고 outer 쪽 recompute는 자기 위치 하나에 대해 한 번만 돈다. → 예/아니오 -> 실 상황에 대한 설명 더 필요함. 이 글만 보아서는 어떤 상황인지 정확히 판단 어려워보임. 판단 보류.

SL-63 — Length 변경은 offset 변경으로만 전파

recompute:Set()하는 대상은 (a) 뒤 형제들의 offset, (b) owner가 Slot이면 그 .Length 둘뿐이고, Length 값 자체는 읽히기만 한다 — 새 전파 채널이 아니다. -> (b) 가 뭔가 이상함. 오너의 length 를 직접 설정하지는 않을것임. 자신 length 를 변경하면, 자동으로 observer 에 등록된것으로 인해 length 가 업데이트 되는 방식일텐데, 그렇지 않음? 최종 리컴퓨팅 결과가 length 가 되는거 아니였음? 그걸 위해 blocker 로 전부 블록 하고도 레이아웃이 멀쩡하지만, 아래에서 offset/length 확인 순회 도는거 아녔음?

SL-72 — dispose 범위에서 Observer/Effect 제외

Observer/Effect는 생존이 gcconn만으로 판정되고 "죽는 순간 트리 부기가 어긋나는" 문제가 원천적으로 없어 dispose 대상이 아니다 — 조기에 끊으려면 unbindLifetime으로 충분하다. -> 우린 조기에 끊는걸 명시적으로 unbindLifetime 로 지원하지 않음. 그건 유저에게 드러나는 표면이 아니고, State<Observer?> 를 사용하는게 적절.

SL-74 — ⚠️ 미해결 항목 확인: SetAndDispose

Get()Set(new) → 옛 값 dispose라는 3단계가 불편하다는 지적에서 나온 source:Apply(SetAndDispose(new)) 또는 source:SetAndDispose(new) 후보가 열려 있고, 전자는 ApplyState가 아니라 Source를 넘겨주는 함수여야 하므로 state:Apply 시그니처에 영향이 갈 수 있어 M3 착수 전 방향만이라도 정해야 한다. -> 타입 문제 때문에 Apply 를 오버라이딩 해서 source 타입을 함수에 건내주는건 못함. 그럼 source -> state 가 안전히 성립 못해서, Apply 라는 이름을 그대로 쓰지는 못함. 따라서 영향이 안 가고, 그냥 SetAndDispose() 로만 Set() 와 세트로 주는게 나아보이고, 그걸로 확정지어야할것 같다는 생각임.

SL-75 — 해제 = 0/None 재등록

별도 unregister API는 필요 없고 setOffsetSource(None)setLength(0) 재등록이 곧 해제다. 순서가 중요한 이유는 값이 틀려져서가 아니라 죽는 중인 Source에 쓰기가 날아가기 때문이고, 해제 시 slot.Offset = nil도 같이 해야 stale한 Offset을 공개하지 않는다. -> 아님. nil 로 만들면 안 되는게, 포탈로 옮기는게 안 됨. 이미 offset 을 들고 가 바운딩 했다면 큰 문제가 생김. 그냥 stale하게 있는게 맞고, 나중에 offset이 멀쩡히 다시 설정되는게 옳음. 언마운트 시 offset stale 은 단순히 맞는 행동이고, 처음 생성 시 0 인것과 유사 동작임.

SL-76 — recomputenil도 관대하게 skip

정상 상태에선 항상 None이 계약이지만 해제/재마운트 전이 구간에서 nil이 관측돼도 크래시 대신 skip이어야 한다 — 계약 완화가 아니라 순수 방어이고 등록 쪽은 여전히 None을 쓸 의무가 있다. -> 말을 더 정리해주길 바람. 애초에 해제에서 nil이 관측 될 일이 없다고 생각하는데, 그게 아니라면 다시 더 자세히 말을 해주길 바람. 이것만으로는 판단이 어려움

SL-78 — nested-Slot 결과의 Length만큼 건너뛰기

updateFn이 nested Slot을 반환하면 그 아이템은 물리적으로 result.Length개를 차지하므로 pos가 그만큼 건너뛴다. 남는 캐비엇은 index가 raw 스냅샷이라 nested Slot의 Length가 outer reconcile 없이 나중에 바뀌면 이후 형제들의 index가 갱신 안 된다는 것이고, 이건 "index는 raw number" 설계의 당연한 연장이라 실시간 정확성이 필요하면 updateFn이 직접 처리해야 한다. → 예/아니오 -> 의미를 모르겠음. 애초에 slot 내의 index 와, length 로 구해진 offset 은 다른 개념인데, 너무 섞어 말하는것 아닌지 생각해보길 바람. 또, Length 업데이트는 상위 slot 이 observe 하기에 형제 slot 갱신에 무관한데, 그 이야기가 아닌것임? LayoutOrder 내에서 index 만 쓰는게 아니다를 말하고 싶은건지, 더 자세히 말해주길 바람.

AT-11 — ⚠️ 열린 항목 확인: Frame { a, a }

같은 그룹 객체를 두 위치에 놓으면 groupKey(v, name)이 그룹 객체별·이름별 메모이즈라 완전히 같은 키가 나와 claim 체크를 통과하고, 두 위치가 하나의 체인을 공유하다가 k=1 retract가 k=2의 바인딩까지 철거한다. Ref처럼 bindLifetime으로 막을 수 없는 이유는 그룹 Attribute 값은 여러 곳에서 쓸 수 있어야 하기 때문이고, 그래서 위치별 claim 레지스트리를 하나 더 두기로 방향은 확정됐으나 키를 무엇으로 할지와 nameClaims와의 공존은 미정이다. -> groupClaimKeys 정도로 확정. 더 나은 답이 있다면 적어주길 바람. 다만 충분하다고 생각하는 이름임.

AT-13 — 해제→재클레임 순서는 Dispatch가 보장

같은 핸들러 재프로세스는 retractor(v)process, 핸들러가 바뀌면 retractFromprocess라 어느 경로든 옛 claim 반납이 먼저다. -> 정확히는 같은 핸들러 재프로세스는 retractor 를 process 에서 굴리고 자기 작업을 함. 따라서 process → calls retractor(v) → process new one 이 맞는걸로 보이는데. 단순 축약상 정보유실인것 뿐이지, 실제 구현은 저렇게 알고 있는게 맞음?

AT-20 — 생존 이름도 매 사이클 철거→재등록

클로저는 인자(새 값)를 안 보고 자기가 등록한 키 전부를 균일하게 철거하며, 비용은 StoreBind 재구독과 같은 값 setAttribute 한 번뿐이다. 그룹 전용 체인이 된 지금은 최적화도 가능하지만 옛 이름 집합을 또 들고 있어야 해 부품이 늘어나므로 기본은 균일 철거 유지다. -> 생존 이름에 대해서 최적화 불가함. 실 value 자체가 바뀌고 같은 값인지 비교를 해야하는데, 그러면 값을 진짜 까봐야 하고, 이전 값을 알아야하기 때문. 이름 목록의 변경으로 최적화가 되는 요소가 아님.

TW-12 — CanAnimate

생략하면 기본 true이고, false로 resolve되면 Tween으로 안 감싸고 self:Get()을 그대로 반환한다 — reduceMotion류 접근성 우회가 이 필드 하나로 표현된다. 케이싱은 나머지 필드와 맞춰 CanAnimate(PascalCase)다. -> 단순 boolean 으로만 설명했는데, 정확히는 CanAnimate: state | boolean | nil 이다. 이미 그럴것으로 보이고, 아니라면 정정해야함. 다른것과 똑같게, 필요 시 바로 Get()

TW-16 — initValue는 에이전트 범위 밖

초기 진입 애니메이션은 필요해지면 사용자가 직접 코드베이스+문서를 만지기로 확정됐고, 에이전트는 임의로 착수하지 않는다. -> 틀리진 않았는데, Human todo 에 언급이 없음.

UI-5 — ⚠️ 확인 필요 항목

숏핸드가 만드는 자식도 quad가 만든 Instance이므로 gcconn/gchold 셋업을 거치는지 구현 시 확인해야 하고, 안 거치면 여기서만 조용히 미아가 된다. -> 애초에 똑같이 process 로 위임하는 이상, gcconn/gchold 없으면 옵저버 바인딩 부터 실패함. 일반 요소처럼 똑같이 UI...{} 처럼 생성되어도 되고, 어떤 방식으로든 gcconn/gchold 가 셋업되는게 맞음. 확인했고 해소된 요소가 될듯.

UI-8 — mapTweenValue가 필요한 이유

vTween<number>wrap 변환을 Tween을 벗기지 않고 .Value에만 적용해야 하므로, table.cloneValue만 교체해 Tween(opts)로 다시 만든다. wrap이 항등인 키도 분기 없이 이 헬퍼를 거친다. -> 그냥 펑터 구조를 그대로 줘도 무방한듯. :Map 정도로써 새 Tween 을 새 관측된 Value 로 형성. Tween:Map(T) -> Tween 가 타입 상 안전히 가능하다. 이는 Tween 에 대한 기본 정의를 두고 다른곳에서 Map 해서 재사용하는 구현도 가능케 할수도 있게 보이긴 하나, 사용 케이스가 넓지는 않을것. 단 외부에 보이는게 무해하고, 어차피 내부 구현 상 필요하므로 같이 만들어도 좋아보임. 혹은 Mapped 의 immutable 의 ed 형태를 써도 좋아보임.

UI-11 — 자식 파괴 시 retractFrom 호출

자식을 파괴할 때 실행 중인 엔진 Tween이 남아있을 수 있으므로 Dispatch.retractFrom(child, prop, 1)을 같이 부르는 게 정석이고, retractor 안에서 다른 키에 대한 retractFrom은 허용된 경로다. → 예/아니오 -> 자식 파괴 시 사실 Tween 은 엔진에 의해 자동 멈춤/무효/삭제 처리되고, 트윈 자체가 retract 되어도 아무것도 안 하는 nop 라 의미가 없을것이다.

ML-5 — 멱등 가드는 module을 키로 하는 Relate

InitXxx가 파일 스코프에 Relate() 하나를 두고 module을 키로 "이 인스턴스에 이미 Init됐는지"를 기록한다. require 캐시로는 부족한 이유는 그게 파일 단위인데 New()는 여러 module 테이블을 만들 수 있어서다. → 예/아니오 -> 새로운 커밋에서 이것이 달라짐.

ML-9

Quad.debug: boolean(기본 false)은 라이브러리 자체의 디버그 스위치이고 지금 게이팅하는 건 핸들러 우선순위 동률 경고 print다. 기본이 false인 이유는 라이브러리가 사용자 콘솔에 아무것도 안 찍는 게 기본이어야 하기 때문이고, 다중 인스턴스화 시 인스턴스별인지 전역인지와 listHandlers()가 이 표면에 속하는지는 미정이다. -> D-8 가 해소시킴

LH-8 — 스코프 판단이 틀렸던 것

"(a) 자기 프로퍼티만 / (b) 서브트리 전체" 중 "(a) 메커니즘은 (b)를 못 준다"는 판단이 틀렸음이 드러났다 — 배열 파트 루프가 각 자식의 마운트를 동기적으로 끝내므로 (a) 메커니즘이 사실상 (b) 스코프를 공짜로 준다. 진짜 경계는 (a)/(b)가 아니라 **"자기 아래 vs 자기 위"**였다. -> 사람이 너무 이해하기 어려운 표현임. 틀린말은 아닌듯 한데, 너무 어려워서 이것만 보고 판단은 안될듯. 풀어 보여줘야함.

OnRendered라는 이름이 componentDidMount 같은거 구현 가능하면 좋긴하겠는데 별로 애매한가 생각중... 화면 그려지기 전에 애니메이션이 된다던가 하지 않게 하는 방안이 있음 좋아보임. 하지만 나중에 얹어져도 좋을 이야기이고, 당장은 사용사례가 안 보이므로 추가 프리미티브에 백로깅만 하고, 나중에 필요하다는 의견이 나오면 재생각 해볼 예정.

destroySlotTree 가 소유를 명시적으로 지워야할 이유가 있냐 의문. Destroy 된 요소는 다른곳에 원래 마운트 못하는게 보통 엔진 정상이고, 또, 릴리즈 안 되어 다른곳에 마운트 막혀도 상관 없고, 그게 정상 동작일 수 있어보임. 정확한 형태가 어떤지 알아봐야할 상황.

Length 를 통해 '먼저 밀어내고 나서' 그 공간에 넣는다가 지금 관행인데, 다른 crud 와 list 도 이게 통하는지 봐야함. 만약 밀어내고 당기지 않은 상태에서 그 공간에 넣는다 하면, 밀어내는걸 구현해야하는 백엔드에서 골치아파짐. 지금 어떤 상황인지 확인해볼것

지금 명시적으로 Attribute(store1, store2, ..., {plain = "table도 됨"}) 로만 되어있고, plain= 에 state/source/T 가 올 수 있음을 안 알려주는 모양으로 나오는데, 그렇게 적어도 된다 생각함. 엔지니어링 비용이 없고, 그냥 이미 그렇게 구현되도록 만들 계획이였다고 생각함. 그리고 뒤에 오는기 이기는 것 또한, 잘 명시되어있나 봐야함.