quad/.claude/session/2026-08-09-08-midreview-defects.md
qwreey 1f56c75978
chore(docs): split CLAUDE.md session log into .claude/session/, keep 2-4 line summaries
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.
2026-08-11 14:40:43 +09:00

9.2 KiB

2026-08-09 여덟 번째 세션 — .claude/base/ 전체 중간검토(질문 모드),

실제 설계 결함 다수 발견·수정

사용자가 "이 프로젝트의 계획을 중간검토합니다. 각 요소들에 대해서 함수나 클래스 등의 동작을 제가 확인 가능하게 리스팅해요... 질문 모드를 쓰면 좋겠습니다"라고 요청 — 2026-08-04 6차 라운드 때 예고해뒀던 "다음 세션 검증 패스"를 실제로 실행한 세션. 서브에이전트 6개를 병렬로 띄워 .claude/base/ 전체(15개 파일, 5296줄)를 클러스터별로 정독시켜 확정된 API/동작을 file:line 인용과 함께 그라운딩된 리스팅으로 뽑아낸 뒤, 6개 배치(Store/State/Source+Dispatch, Ref/PreRef+Brand+Length-Offset+생명주기, Modifier, Slot, Tag/Attribute/UI shorthand+Blocker/Effect, 컴포넌트 경계+아키텍처)로 나눠 각 배치를 텍스트로 보여주고 바로 AskUserQuestion (문제없음/문제있음)으로 확인받는 방식으로 진행 — 문제 제기된 건 그 자리에서 바로 문서에 반영(끝까지 미루지 않음). 총 24개 확인 질문 중 약 1/3에서 실제 설계 결함이 나옴 — 전부 사용자가 구체적인 반례/Luau 시맨틱스를 근거로 지적한 것이라 전부 그대로 수용, 방어하지 않고 수정.

발견·수정된 것 (파일별):

  • base/bind-system-plan.md (가장 많이 고침):
    • Source(default)/Ref(default)default 생략이 "선택"이라는 서술에 "T가 nilable일 때만 안전하다"는 캐비엇 누락 — 추가. Ref:Callback이 등록 즉시 발화해서 이 문제가 더 잘 드러남.
    • Dispatch 체인 절에 "handler.processDispatch.process 없이 직접 호출하면 UB(체인 bookkeeping이 깨져 retract가 영영 안 불리거나 정합성이 무너짐)"라는 불변식이 안 적혀 있었음 — 추가.
    • Ref 콜백/대기자 배열의 소진 슬롯을 None에서 nil로 되돌림 — 2026-08-07 열 번째 세션에 "구멍 있는 정수 키는 순회 순서가 깨진다"는 이유로 None으로 바꿨던 게 이 배열엔 안 맞는 처방이었음(사용자 지적): 이 배열은 순서가 안 중요해서 일반화 for가 구멍이 있어도 전부 방문하고, 오히려 None을 쓰면 슬롯이 영원히 안 비어서 :Wait()마다 배열이 끝없이 길어지는 새 문제가 생김 — nil로 지우고 빈 슬롯을 재사용하는 등록 함수로 바꿈. PreRef pre-pass/ Length-Offset의 sourceList는 순서가 실제로 중요해서 계속 None이 맞음 — 두 사례를 헷갈리지 않게 교차 참조로 명확히 구분.
    • .Value가 평범한 hash 필드가 아니라 __index로 구현돼야 하는 이유(콜백 배열과 같은 테이블에 있으면 T가 함수/스레드일 때 콜백 처리 루프에 오분류될 위험) 추가.
    • isRef/isPreRefisState/isSource와 같은 상위-하위 합성 패턴으로 재정정 — 원래 "서로 배타적인 형제 브랜드"였는데, SourceState를 만족하듯 PreRefRef 런타임을 재사용하는 관계라 같은 방향(하위=PreRef가 상위=Ref에 포함)으로 다뤄야 일관적이라는 지적 — isPreRef가 가장 구체적인 항등, isRef는 그 위에 얹힌 상위 개념. (v=Ref) children leaf 매치 핸들러는 이제 isRef(v) and not isPreRef(v)로 명시적으로 좁혀야 함.
    • NoneHandlerk 타입을 안 가리는데 왜 배열 파트 None(숫자 키)에 실제로 안 걸리는지 명확화(배열 파트 None은 애초에 Dispatch.process를 안 타서 NoneHandler가 볼 기회 자체가 없음).
    • setLength/setOffsetSourceNone 페어링 대상을 "Ref/PreRef 등" 예시 목록에서 "그 배열 위치의 값 자체가 None인 모든 경우"로 명시적으로 확장, 둘이 항상 짝을 맞춰야 한다는 점도 재강조.
    • :Subscribe()가 quad 전역 GC-native 원칙의 의도적 예외(참조를 다 놓아도 GC 안 되고 계속 실행됨)라는 경고가 없었음 — 추가, 용도도 "완전히 top-level" 케이스로 좁혀 문서화.
  • base/modifier-plan.md: 핸들러 계층 값 → error 체크가 State<Ref>류 "State/Source가 감싼 내부 값"까지는 못 잡는다는 한계 — 명시적 UB로 문서화(오버엔지니어링 방지, 실사용 위험 낮음).
  • base/slot-plan.md (가장 큰 변경): CRUD 식별 기준을 element 레퍼런스에서 인덱스 기준으로 전환Remove(index)/ Extract(index, newElement?)/Move(oldIndex, newIndex). 원래 "인덱스는 stale해진다"는 이유로 레퍼런스 기준을 택했는데, 실제로는 반대(호출부가 Add 리턴값을 안 담고 흘려버리는 경우가 흔함)가 더 큰 문제였음. ExtractAll()/Get(index)/IndexOf(element) 신설 (Get은 "YAGNI"로 드롭했던 걸 재추가). Extract(index, newElement?) 신설 — 교체가 필요하면 기존엔 Extract+Add 이중 O(n) 시프트가 필요했는데, 이제 O(1) 제자리 교체 가능(이전 element 반환).
  • base/tag-plan.md: TagHandler.retract의 전체 삭제 동작이 정확히 v == nil일 때만 맞다는 전제를 assert로 명시(기존엔 "v를 안 봐도 됨"이라고만 서술돼 있어 조건이 암묵적이었음).
  • base/attribute-plan.md, .claude/question.md: 타입 파라미터화(Attribute<<T>> 제네릭 vs BooleanAttribute류 정적 패밀리) — "미확정"에서 "둘 다 채택"으로 확정(내부 구현 동일, 호출부 표기만 다름). = 뒤 값 타입까지 narrowing되는지는 M0/M10 실측 필요(안 돼도 런타임 무관)로 명시.
  • base/ui-shorthand-plan.md: UICorner/UIPadding/UIScale이 타입 생성 스크립트가 만드는 FrameModifier류 타입의 메소드 목록에도 포함돼야 한다는 체크리스트 항목 추가(런타임과 무관한 순수 타입 생성 디테일).
  • base/effect-plan.md: EffectHandle이 내부 Observer를 필드로 강참조한다는 것, bindLifetime/:Subscribe() 둘 다 state가 있으면 내부 Observer까지 cascade해야 한다는 것(안 그러면 내부 Observer의 canExecute 게이팅이 올바른 inst를 못 봄) — 재확인 후 명시화.
  • base/component-composition-plan.md (Length/Offset 다음으로 많이 고침):
    • "리프 바인딩엔 Source가 좁은 예외"라는 서술이 틀림 — 정정. local a = Source(true); Frame { Visible = a }; a:Set(false)처럼 Source를 리프에 직접 물리는 건 이미 확정된 "Source가 State를 구조적으로 만족" 원칙이 그대로 커버하는 정상 경로였음 — "State가 일반적"이라는 서술은 Source를 못 쓴다는 뜻이 아니라 "여러 값에서 파생된 계산 결과는 State일 수밖에 없다"는 통계적 경향 서술일 뿐이라고 재정정.
    • props.Modifier or None 관용구의 None 근거 포인터가 Ref 콜백 배열 정정으로 깨질 뻔한 걸 교차 참조로 바로잡음(그 배열은 순서가 중요한 별개 케이스라 None이 계속 맞음).
    • Frame { Comp{} }에서 CompSlot을 반환하는 다중 루트 우회 경로가 새 배선 없이 그대로 작동함을 재확인(값이 컴포넌트 호출로 왔든 리터럴이든 디스패치 입장에선 구분 없음).
  • ROADMAP.md: 위 Ref Nonenil/isRef·isPreRef 변경사항 체크박스 동기화.

변경 없이 확인만 된 것: :With/:Compute 체이닝, None 센티널 기본 메커니즘, Length/Offset 전체, 이중 바인딩 금지/Relate/생명주기, Modifier setter/Apply/Overridden 판단 기준, Peek/isState/None setter 인자, Slot 요소 타입 제약/Extract portal/Length, Slot:List 시그니처(단, 캐스케이드 성능 이슈는 keyFn 명시 유도로 이미 문서화돼 있어 추가 조치 불필요), List 구독 lazy 시점, Tag 값 모양/패키지 배치, Blocker 전체, 소스트리/네이밍 컨벤션/Handler 3분류/테스트 전략/이식성 원칙.

부수 기록: .claude/memory(세션 간 영속 기억)의 협업 스타일 메모에 이번 리뷰 진행 방식(에이전트 병렬 추출 → 배치별 텍스트+AskUserQuestion 즉시 확인 → 그 자리에서 바로 문서 반영)을 다음에 재사용할 패턴으로 기록 완료.

다음 세션이 할 일: 안 바뀜(ROADMAP.md M0부터) — 이번 세션은 설계 확정이 아니라 기존 확정 사항의 결함 수정이었지만, 결과적으로 M0 착수 전 상태가 더 탄탄해졌을 뿐 우선순위 자체는 그대로. 이 중간검토가 마지막 배치(6단계)까지 끝났는지, 사용자가 이어서 더 볼 부분이 있는지는 다음 세션 시작 시 확인.