CLAUDE.md had grown to 3196 lines of accumulated session logs, causing context bloat. Full session narratives (including trial-and-error and later-corrected reasoning — quadnomicon devlog raw material) now live as 39 individual files under .claude/session/. CLAUDE.md keeps only a short "지금 할 일" (re-synced against question.md/pre-implementation-audit.md, stale detail dropped) and a compact per-session summary+link table. No design decisions changed; base/research/question.md were already in sync with every session (verified against README.md/question.md before archiving), so no unreflected content needed migrating first.
4.6 KiB
2026-08-07 세 번째 세션 — Ref의 KV 핸들러 처리 vs phase 타이밍, PreRef 신설
출발점: Ref가 Modifier처럼 밖에서 처리되는 게 아니라 KV 핸들러
(process(inst,k,v))로 처리된다면, "생성 직후"/"자식 마운트 후" 두
콜백 타이밍(특히 self(Instance)를 안 주는 이벤트가 Ref로 self를 얻는
경우)을 단순 for-loop 디스패치만으로 어떻게 표현하는지가 출발 질문 —
길게 이어진 단일 스레드라 아래 요약만 읽으면 됨, 상세 근거는 각 base
문서에 이미 반영됨.
핵심 결론(전부 base/bind-system-plan.md에 반영 완료):
- base 디스패치 드라이버는 props 순회를 "배열 파트(children/Ref) 먼저, 해시 파트(프로퍼티/이벤트) 나중"으로 명시적으로 두 패스 계약화한다 — Luau 테이블이 실제로 이렇게 순회되는 걸 사용자가 직접 확인했지만, 그 우연한 동작에 기대지 않고 base가 스스로 이 순서를 보장(다른 백엔드가 다른 자료구조를 쓸 수 있어서). M0 스파이크 검증 항목에 추가.
CreatedRef의{phase="created"|"mounted"}옵션은 폐기. 두 패스 계약 덕에 "자식 마운트 전/후"는 그냥 배열 안에서 Ref를 다른 children 보다 앞/뒤에 놓는 것만으로 공짜로 표현됨 — 옵션 문법 자체가 불필요.PreRef신설 — "프로퍼티/이벤트 세팅보다도 먼저"(Roblox의ChildAdded/DescendantAdded/Changed류가 setup 도중 동기 발화할 수 있어서 self-ref가 이벤트보다 먼저 채워져야 하는 케이스)만 담당하는 별도 nominal 타입.Ref를 그대로 재사용(런타임 중복 없음)하되 Modifier 필드 값·Source/Store 값으로는 타입으로 아예 못 들어가게 막고, children 배열 안에서도 위치 무관하게 항상 최우선(호이스팅) — base 드라이버가 두 패스 루프 앞에PreRef만 골라 fire하는 좁은 pre-pass를 하나 더 둠.- 일반
Ref는 Modifier/Store 어디든 계속 자유롭게 들어감 — Store를 통해 나중에 도착하는 Ref는 그냥 도착한 순간 처리, 별도 phase 개념 불필요. :Wait()는 PreRef에도 그대로 유효 — fire 자체는 동기적이지만 호출부가task.spawn이 아니라 순수coroutine일 수 있어 실제 yield-resume이 필요한 경우가 있음. "채워졌는지 먼저 확인, 없으면:Wait()" 방어 관용구를 문서화 대상으로 명시.- 콜백/대기자 실행 구현 디테일 추가: 같은 배열 하나를 한 번의
일반화
for로 순회하며type(v)=="thread"면coroutine.resume+ 슬롯 nil 처리(1회성), 함수면 그냥 호출(유지) — 새 등록은table.insert로 끝, 성긴 배열이어도 압축 불필요. [정정, 2026-08-07 열 번째 세션] "슬롯 nil 처리"는 틀림 — 사용자가 Luau REPL로 반례 제시, 실제로는None으로 소진해야 함(#t/table.insert안전성 문제).base/ bind-system-plan.md"왜nil이 아니라None인가" 절이 최신. - v1의
OnCreated특수 DI 키는 이식 안 함 —Ref():Callback(fn)으로 완전 대체.
역전된 이전 서술은 archive로 이동: CreatedRef의 phase 옵션과
"Ref는 특수 처리 없는 평범한 참가자"라는 원래 서술은
archive/ref-phase-option-reversed.md로 옮기고 원 위치엔 짧은 포인터만
남김(컨텍스트 비대화 방지 목적, archive/store-source-proxy-reversed.md와
같은 패턴). architecture.md 소스트리 주석/question.md(PreRef를
용어 재검토 대상에 추가)/research/documentation-content-map.md(stale
{phase=...} 예시 갱신)도 같이 동기화함.
아직 미해결, 다음 세션 주제로 예고됨: { Override = nil, mod }처럼
인라인 키로 modifier가 주는 값을 명시적으로 "지우고" 싶어도 Lua
테이블 리터럴의 키 = nil은 키가 아예 없는 것과 구별이 안 돼서 안
풀리는 문제 — false를 이벤트 disconnect 센티널로 쓴 선례처럼 None
(가칭) 프리미티브를 도입하는 방향만 base/modifier-plan.md "2-1"절에
짧게 메모해두고 상세 설계는 다음 세션으로 미룸.