diff --git a/.claude/base/bind-system-plan.md b/.claude/base/bind-system-plan.md index a18869f..67b6b6f 100644 --- a/.claude/base/bind-system-plan.md +++ b/.claude/base/bind-system-plan.md @@ -582,6 +582,20 @@ quad-web의 해당 Handler는 offset 변경 관측 시 아무것도 안 하는 n `base/slot-plan.md`의 "여러 Slot이 섞일 때 순서 보장" 절이 이 메커니즘으로 해소됨 — 상세는 그 문서 참고. +**동적 자식 추가/제거의 유일한 정당 경로는 `Slot` 또는 `state`류 +store-bind — 그 외 방식은 UB로 확정(2026-08-10 세션).** `Length`/`Offset` +카운팅은 그 위치를 담당하는 Handler(`Dispatch/Slot.luau`, store-bind +프로퍼티 핸들러)가 `Dispatch.setLength`/`Dispatch.setOffsetSource`를 +호출해줘야만 정합적으로 유지됨 — 이 두 API를 부르지 않고 quad가 관리하는 +부모 Instance에 자식을 끼워 넣는 경로(예: 사용자 코드가 `newInst.Parent = +parentInst`를 직접 호출해 Slot이 마운트해둔 부모 밑에 자식을 몰래 +추가/제거하는 것)는 `lengthList`/`sourceList`가 그 변화를 전혀 모르게 +만들어 카운트·형제 순서 계산이 조용히 어긋남 — 별도 방어 로직 없는 UB. +`Slot`이든 `state`이든 둘 다 이미 이 두 API를 정확히 호출하는 +유일한 정당 경로로 확정돼 있음(위 `setLength`/`setOffsetSource` 절 +참고) — 새 경로를 만들 필요 없이 "동적 자식은 반드시 이 둘 중 하나를 +거쳐야 한다"는 규칙만 문서화하면 됨. + ## Store 바인드는 특수 경우인가, 아니면 pluggable 바인드를 재실행하는 래핑인가 사용자 원 메모: "스토어 바인드는 특수 경우로 둘지, 아니면 다른 pluggable 바인드를 diff --git a/.claude/base/slot-plan.md b/.claude/base/slot-plan.md index 2b7bab2..b29d5fc 100644 --- a/.claude/base/slot-plan.md +++ b/.claude/base/slot-plan.md @@ -666,6 +666,14 @@ UI에 직접 관측, (2) `Dispatch.setLength(inst, i, slot.Length)`가 형제 마운트된 것"만 반영 — 수동 Visible 토글을 쓰면 `Length`가 그걸 못 잡는 게 맞고, 그건 사용자가 별도 State로 계산해야 하는 몫. +**동적 자식은 반드시 `Slot` 또는 `state`류 store-bind를 통해서만 +추가/제거 — 그 외 경로는 UB(2026-08-10 세션, `base/bind-system-plan.md`의 +"Length/Offset" 절 반영).** 둘 다 `Dispatch.setLength`/`setOffsetSource`를 +정확히 호출하는 유일한 정당 경로라, 이걸 우회해서(예: 외부 코드가 Slot이 +마운트해둔 부모 Instance에 직접 `.Parent = parentInst`로 자식을 끼워 +넣는 것) 자식을 추가/제거하면 `Length`/형제 순서 계산이 그 변화를 몰라 +조용히 어긋남 — 별도 방어 로직 없음, 문서 경고로만 남김. + ## 백로그 — `Slot():Single(state, updateFn?)` (2026-08-09 여섯 번째 세션, 미착수) `:List`의 key-map(`mounted`/`userdata`/`keyIndex`) 없이 "0개 아니면 1개"만 diff --git a/CLAUDE.md b/CLAUDE.md index f8a641d..561ac59 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2492,3 +2492,29 @@ M0가 공식적으로 짜야 할 스파이크(위 "지금 할 일" 1번, `ROADMA **다음 세션이 할 일**: 사용자가 luau-test 실행 결과를 갖고 오면 그것부터 반영. 아직 없으면 `ROADMAP.md` M0 착수 우선순위는 그대로(위 "지금 할 일" 1번 참고) — 단, 이 폴더 결과를 먼저 확인하고 진행하는 게 순서. + +## 2026-08-10 세션 — 동적 자식 추가/제거는 `Slot`/`state`만 정당, +그 외는 UB로 명문화(문서 갭 보강) + +사용자 질문에서 시작: Slot이 마운트한 객체 수를 `Length`/`Offset` +누적합으로 세는 방식(2026-08-09 여섯 번째 세션 확정)이 되면서, 이 카운팅을 +안 거치고 quad가 관리하는 부모 Instance에 외부에서 직접 `.Parent = inst`로 +자식을 끼워 넣는 게 UB로 문서화돼 있는지 확인 요청 — 검토 결과 **문서 +어디에도 명시돼 있지 않은 진짜 갭**이었음(기존 UB 목록엔 Handler 순환/ +이중 바인딩/`Dispatch.process` 우회 직접 호출/`setLength`·`setOffsetSource` +생략 등은 있었지만 이 케이스는 빠져있었음, 인접했던 "수동 Visible 토글은 +Length가 못 잡는 게 맞다"는 캐비엇은 이미 마운트된 element를 나중에 +숨기는 별개 시나리오라 이것과 다름). + +**확정**: 동적 자식 추가/제거의 유일한 정당 경로는 `Slot` 또는 +`state`류 store-bind 뿐 — 둘 다 그 위치의 Handler가 +`Dispatch.setLength`/`Dispatch.setOffsetSource`를 정확히 호출하는 것으로 +이미 보장돼 있음. 이 두 경로를 거치지 않고 quad가 마운트해둔 부모 +Instance에 직접 `.Parent =` 대입으로 자식을 넣거나 빼면 `lengthList`/ +`sourceList`가 그 변화를 전혀 몰라 `Length` 카운트와 형제 순서(offset) +계산이 조용히 어긋남 — 새 방어 로직 없이 UB로 문서화만 함(다른 UB +케이스들과 같은 톤). `base/bind-system-plan.md`("Length/Offset" 절 +말미)/`base/slot-plan.md`("Slot.Length" 절 말미)에 반영 완료. + +**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터, luau-test 결과 확인 +우선) — 이번 세션은 순수 문서 갭 보강이라 우선순위엔 영향 없음.