quad/.claude/question.md
qwreey 41b96e6917
design: QA 4라운드 회신 1차 반영 — 즉시 처리 가능한 정정 20건
사용자 회신(pre-implementation-qa-round4-response.md) 중 판단이 명확한
항목을 base/에 반영. 굵직한 것만:

- LP-1: quad의 `Connected`는 "계산된 속성"이 아니라 그냥 RBXScriptConnection의
  네이티브 필드다 — rbvm 프록시 사정을 잘못 옮겨온 서술이었음. 실제 판정은
  "gcconn이 없음" / "있는데 Connected==false" 두 상태뿐.
- D-56: `bindLifetime`의 첫 인자가 Instance가 아닐 수 있다(Slot-in-Slot의
  ownerKey) — 백엔드가 반드시 핸들링해야 하는 요구사항으로 신설. gcconn
  트릭이 안 통하므로 세 번째 판정 분기가 필요하다는 것까지 명시.
- SL-75/D-60: 언마운트 시 `slot.Offset = nil`은 포탈을 깨뜨림(이미 구독
  중인 다운스트림이 영구히 끊김) — stale하게 두는 게 맞고, 마운트 전 기본값도
  nil이 아니라 0.
- SL-74: `SetAndDispose`는 `source:SetAndDispose(value)` 콜론 메서드로 확정.
  `Apply` 오버라이딩은 Source→State 단방향 때문에 타입이 안 성립.
- E-11: leaf 바인딩된 Effect엔 `:Unsubscribe()`가 아예 안 먹는 것으로
  Observer와 통일. 옛 "(3) 이후 leaf가 죽어도 중복 호출 안 됨"은 이중 바인딩
  게이트상 성립할 수 없는 문장이라 삭제.
- AT-20: 생존 이름 최적화는 "부품이 늘어나서 안 하는" 게 아니라 값 비교가
  필요해 **원리적으로 불가능**하다(State 계약상 `:Get()` 비교 금지).
- UI-8: `mapTweenValue` 로컬 헬퍼를 `Tween<T>:Map(fn)` 공개 메소드로 승격.

부수로 D-3(retract가 깊은 인덱스부터인 이유)/D-10(두 패스를 명시하는 진짜
이유는 순서를 못 믿어서가 아니라 이식성)/LH-8(자기 아래 vs 자기 위)을 코드
예시로 풀어 썼고, TW-16으로 HUMAN_TODO에 initValue 항목을 신설했다.

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

19 KiB

확인/결정 필요 목록

이 문서는 사용자가 답해야 할 것만 담습니다. 이미 결정이 끝난 항목은 2026-08-13 아홉 번째 세션에 archive/question-resolved.md로 옮겼음 — "해결된 게 너무 많아 필요한 부분만 읽기 어렵다"는 사용자 지적에 따른 것. 결정 내용은 안 바뀌었고 읽는 자리만 옮겼으니, 어떤 항목이 왜 그렇게 정해졌는지 되짚고 싶으면 그 문서를 볼 것(지금 유효한 설계 자체는 항상 base/가 소스).

항목을 해소하면: 여기서 지우고 archive/question-resolved.md에 근거와 함께 옮길 것 — 다시 쌓이면 같은 문제가 반복됨.


최우선 — 없음 (2026-08-14 열한 번째 세션 기준)

M0 착수를 막던 항목이 전부 해소됐습니다. 0-Y(:Compute(fn)의 lazy 핸들 계약)는 열세 번째 세션에, 0-Z(Attribute 이름 소유권)와 0-A(재디스패치 하강 diff)는 열네 번째 세션에, 0-W(Ref 이중 배치 방지)는 2026-08-14 열한 번째 세션에 확정·base/ 반영 완료 — 그래서 이 문서엔 이제 "결정 대기" 절 자체가 없음(비어서 헤딩째로 삭제). 해소 전 원문과 결론은 archive/question-resolved.md, 뒤집힌 옛 재디스패치 모델은 archive/dispatch-hintvalue-model-reversed.md.

M0 착수 전 읽을 것: base/typing-limits.md(0-Y가 남긴 구현 규약), base/dispatch-core-plan.md(0-A/0-Z가 반영된 디스패치 코어 — 열네 번째 세션에 bind-system-plan.md에서 분리 신설).

1. 용어 정리 — 아직 안 정해진 것만 (사용자 요청, 진행 중)

사용자 원 메모: "quad는 register라던가 좀 부정확하거나 느낌이 바로 와닿지 않던 용어들이 많음 — 전체적 용어를 보고 생각해볼래? 제안을 줘, 나도 같이 볼게." 이미 확정된 이름(State/Relate/List/canBound/Ref/ PreRef/Peek/isState/None/NoneHandler/Handler)의 근거는 archive/question-resolved.md. (canBound는 2026-08-14 다섯 번째 세션에 폐기돼 canExecute로 통합됐다가 같은 날 열한 번째 세션에 별도 진입점으로 재도입되어 여전히 이 목록에 있음 — 이중 바인딩 가드 전용이고 canExecute (emit 게이팅 전용)와는 판정 로직만 공유. 아래 3번 canExecute 항목과 base/lifecycle-pattern.md의 "canBound vs canExecute" 절, archive/canexecute-inst-arg-reversed.md 하단 addendum 참고.)

  • [해소됨, 2026-08-18] DID(Declarative) 확정 — 원문과 근거는 archive/question-resolved.md. 요지: DI가 "Dependency Injection"과 완전히 겹쳐 실제 오해 전례가 있었고, D는 Instance 전용이 아닌 declare 요소 전반으로 확장 가능하며 D.FrameModifier류 타입 프리픽스도 짧게 유지된다. 미뤄뒀던 유일한 사유(한 글자 식별자의 검색성/자기설명력)는 "문서에서 처음 나올 때 항상 D(Declarative)로 풀어쓴다"는 표기 규약으로 보완하기로 같이 확정. 코퍼스 반영 완료 — base/bind-system-plan.md의 "인스턴스 생성 / 이벤트 네이밍 인체공학" 절.
  • [해소됨, 2026-08-19] PopOnlyDetach 확정 — 원문과 근거는 archive/question-resolved.md. 요지: 이미 있는 Extract(호출자가 직접 부르는 명령형 추출)와 동사가 겹치면 헷갈리는데, Detach는 "화면(부모 계층)에서만 떼어낼 뿐 관리 주체는 여전히 reconcile"이라는 뜻이라 Extract의 "소유권을 통째로 넘긴다"와 자연스럽게 구분되고, nil(파괴)과의 대비도 더 직접적으로 드러남. 공개 표면 위치도 같이 확정 — Slot이 함수(팩토리)라 Slot.Detach처럼 붙이려면 callable-table+메타테이블이 새로 필요해서 과함, None과 같은 선례를 따라 패키지 최상위 export로. 코퍼스 반영 완료 — base/slot-plan.md의 "nil 리턴은 파괴가 기본" 절.
  • Slot(2순위): Vue의 "slot"(콘텐츠 주입 지점)과 이름은 같지만 의미가 다름(quad의 Slot은 자식 배열 재조정 프리미티브) — Vue 배경 있는 사람이 헷갈릴 수 있음.
  • canExecute(3순위, 사소함): 실제로 "이 값이 아직 살아있나" 확인인데 이름이 범용 권한 체크처럼 들림 — isAlive 쪽이 더 직접적이라는 제안이 있었으나, (2026-08-08 재검토) isAlive는 top-level isX 계열 (isState/isRef/isPreRef/isModifier/isObserver류 — 전부 타입 판별자)과 접두어가 겹쳐 "이것도 타입 체크인가" 오해를 유발할 수 있다는 점이 지적됨. canExecute는 타입이 아니라 liveness(생존 여부)를 묻는 질문이라 is보다 can 계열 접두를 유지하는 쪽이 낫다는 방향으로 사용자가 기욺 — 여전히 미확정, 다음에 can으로 시작하는 구체 대안(예: canRun)을 같이 검토할 것. [2026-08-14 다섯 번째 세션] 시그니처는 canExecute(value): boolean 1-인자로 확정(옛 (inst, value) 2-인자는 폐기, base/lifecycle-pattern.md, archive/canexecute-inst-arg-reversed.md). [2026-08-14 열한 번째 세션 정정] 당시엔 canBound가 폐기돼 canExecute 하나가 두 역할을 겸했으나, 이후 canBound가 별도 진입점으로 재도입되며 다시 갈라짐(같은 문서의 "canBound vs canExecute" 절) — 이 이름 정리 항목은 이제 canExecute(emit 게이팅 전용)에만 적용되고, canBound(이중 바인딩 가드 전용)는 별개 이름으로 유지됨. 둘 다 판정 로직은 비공개 헬퍼 하나를 공유.
  • 클로저 인자 이름 hintValue(3순위, 사소함, 2026-08-13 열네 번째 세션 신설): 하강 diff 재디스패치에서 이 인자는 더 이상 "힌트"가 아니라 nil이거나 같은 핸들러가 곧 처리할 새 값임이 계약으로 보장됨(base/dispatch-core-plan.md) — 이름이 옛 모델의 잔재라 nextValue류가 더 정확함. 코퍼스에 이미 널리 쓰인 이름이라 이번엔 안 바꾸고 대기열에만 올림(의사코드는 새로 쓰는 자리부터 nextValue를 쓰기 시작했음).
  • Brand(3순위, 사소함, 2026-08-07 여덟 번째 세션 추가): 런타임 nominal 타입 판별 통합 메커니즘(Brand.set/Brand.get, isState를 branded 타입 전부로 일반화) — brand-plan.mdBrand 절에서 동작/구현 방식은 확정, "OOP 인스턴스의 클래스명을 얻는 느낌"을 전달할 더 나은 이름이 있는지가 열린 질문(사용자가 직접 제기) — Tag는 이미 quad-roblox의 CollectionService 래퍼로 쓰여서 이름 충돌, 후보로 "type namespace"류를 사용자가 검토했으나 미확정. (2026-08-08 재확인) 사용자가 다시 짚었지만 여전히 미정.
  • Tag/Added/Removed/Merged(3순위, 사소함, 2026-08-08 세 번째 세션 array-part 값 객체 재설계 때 확정된 API 표면): base/tag-plan.md가 "열린 질문 없음, 값 모양/메커니즘/retract/패키지 배치 전부 확정, 이름 자체만 용어 정리 대상"이라고 명시해뒀으나 이 목록에 반영이 안 돼 있던 누락 — 이번에 추가. Tag는 Roblox CollectionService가 쓰는 용어와 1:1 대응이라 그 자체로는 무난해 보이지만, 위 Brand 항목(97-99행)에서 "Tag가 이미 이 뜻으로 쓰이고 있어서 충돌"이라는 이유로 Brand의 대안 이름 후보에서 제외됐다는 점은 참고할 것 — 두 이름이 같은 코퍼스 안에서 공존 가능한지도 같이 검토 대상.
  • Attribute/AttributeKey(3순위, 사소함, 2026-08-11 아홉 번째 세션 추가): 여러 Store를 한 번에 attribute로 묶는 그룹 프리미티브 (Attribute(store1, store2, ...), Tag와 동형)가 신설되면서, 기존 단일 키 생성자 Attribute<<T>>("name")를 이름 충돌 방지를 위해 AttributeKey<<T>>로 잠정 리네임함(OnChange/OnChangeKey처럼 함수 이름과 반환 타입 이름이 분리된 기존 전례와 대칭) — 해석 모호성 자체는 이미 없앴으니 급하지 않지만, 최종 이름은 여전히 이 목록의 다른 가칭들과 함께 검토 대상. base/attribute-plan.md "그룹 Attribute(...)" 절 참고.
  • 참고 — 이미 지나간 사례: register(v1) → State(v2) 리네임은 "모호함"은 풀었지만 "다른 뜻으로 이미 쓰이는 단어"라는 새 문제를 만든 셈 — 이번 정리에서 같은 패턴을 조심할 것.
  • Store/Source/Modifier/process/retract/isHandlable은 업계 선례와 잘 맞거나 이미 신중하게 결정된 이름들이라 특별한 문제 없음.

3. 낮은 우선순위 — 열려 있지만 급하지 않음

  • [신설, 2026-08-18 구현 전 QA 3라운드] M2가 M3의 Blocker.luau에 구조적으로 의존하게 됨 — 이대로 각주만 두고 로드맵 순서를 유지할지, Blocker.luau(또는 최소 표면 On/Off/IsOn/OffWithoutEmit)를 M2로 앞당길지, M2/M3 경계 자체를 재검토할지. RC-1의 Blocker 게이팅 해법 때문에 ROADMAP.md M2의 Dispatch.setLength/setOffsetSource 체크박스가 getBlocker/:On()/:IsOn()/:OffWithoutEmit()을 호출하는데, 정작 Blocker.luau 자체는 M3 체크박스에 있다 — 로드맵 순서대로면 M2가 아직 없는 걸 참조하게 된다. 지금은 M2 체크박스에 이 사실만 각주로 남겨둔 임시 조치(가장 보수적인 선택, 마일스톤 재편은 안 함) — M2 착수 전 필요. 상세는 qa-request/pre-implementation-qa-round3.md의 "ROADMAP.md 마일스톤 정합성" 절.
  • Operator 콤비네이터 슈가 네임스페이스 이름+포함 범위(2026-08-12 신설, 같은 날 후속으로 외부 리서치 완료)Sum/Product/Not/비트연산 등 :Compute/:Apply용 슈가 함수 모음의 이름. 흔한 단어라 top-level 노출은 위험, 후보는 Operator/Op/Ops(Combinator는 코퍼스 전반에서 이미 일반명사로 쓰여서 제외) — 서브 에이전트 외부 리서치 결과 Operator가 가장 선례가 강함(Python operator 모듈)이나 최종 확정은 여전히 사용자 몫. 같은 리서치에서 포함 범위도 새로 갈렸음 — 비트/비교 연산자 그룹과 Sub/Div는 리액티브 콤비네이터로서 선례가 전혀 없어 드랍 후보로, Clamp/Min/Max는 선례가 강해 추가 후보로, Debounce/Throttle은 업계에 흔하지만 Blocker와는 다른 시간 기반 메커니즘이라 이 카탈로그 밖 별개 질문으로 분리됐었음 — [2026-08-19 해소] 그 별개 질문은 base/debounce-throttle-plan.md로 전부 해소·승격 완료, 더 이상 판단 대기 아님. [2026-08-13 세션 신설] Alternative(nil 대체값, coalesce/??/ 엘비스 연산자류) 후보 추가 — Haskell 비교 리서치 중 나옴, 카탈로그 확정 규칙에 그대로 맞아 포함 근거는 있음. 상세는 research/operator-sugar-plan.md. 구현 자체는 맨 마지막 우선순위(순수 슈가, 없어도 무방) — 여전함.
  • 중첩 State 평탄화 State<State<T>>State<T>(2026-08-13 여섯 번째 세션 신설, 백로그)[근거 축소, 열네 번째 세션] 원래 이 항목의 주 근거는 "깊은 체인에선 힌트가 nil로 전달돼 깜빡임 방지가 꺼진다"는 실제 기능 손실이었는데, 하강 diff 재디스패치로 각 레벨이 자기 값을 받게 되면서 그 손실 자체가 없어졌음(base/dispatch-core-plan.md). 남은 근거는 편의성과 Slot offset이 밀리고 당겨지는 케이스뿐이라 우선순위가 더 내려감 — state:Flatten()류 콤비네이터 아이디어는 그대로 백로그. 상세는 research/operator-sugar-plan.md 마지막 절.
  • [신설, 2026-08-18 커밋 전 /code-review high] store:GetDynamic을 콜론 메소드로 둘지, 탑레벨 함수로 둘지 — 콜론 메소드로 두면 Store의 lazy __index(없는 키를 인덱싱하면 그 자리에서 Source를 만들어 저장)와 부딪혀서, __index가 고정 메소드 테이블을 먼저 확인해야 하고 그 결과 GetDynamic이 모든 Store의 예약 키 이름이 된다(그 이름의 Source는 dot-access로 못 만듦). Store 키는 사용자 도메인 데이터 이름이라 충돌 확률이 Modifier의 예약 이름들보다 높다. 대안은 탑레벨 getDynamic(store, name) — "특정 프리미티브에 안 묶인 범용 유틸은 소문자 탑레벨"이라는 기존 네이밍 규칙에는 오히려 더 맞는다. M3/M4 착수 전 필요, base/store-plan.md의 "타입 추론 문제" 절.
  • [신설, 2026-08-18 커밋 전 /code-review high] Detach로 홀드 중이던 요소의 키가 데이터에서 사라지면 어떻게 처분하는가 — 지금 의사코드대로면 mounted[key]가 이미 nil이라 파괴 대상이 아니고, 소멸 루프가 userdata[key]까지 지워서 파괴되지도 updateFn에게 되돌려지지도 않고 참조만 끊긴다. 같은 절의 표("키가 사라지면 파괴")와도, "버릴 시점은 updateFn이 정한다"와도 어긋남. 선택지 (a) 소멸 루프가 userdataold까지 확인해 파괴, (b) 지금 동작(참조만 끊고 GC)을 정식화하고 표를 고침, (c) updateFn을 마지막으로 한 번 더 불러 처분을 물음. [정정, 2026-08-18 /code-review high] M6(:List가 있는 마일스톤) 착수 전 필요 — M8(Ref) 아님, base/slot-plan.md의 "nil 리턴은 파괴가 기본" 절.
  • [신설, 2026-08-18 구현 전 QA] 그룹 Attribute의 위치별 claim 설계 — 같은 그룹 객체를 두 위치에 놓는 경우(Frame { a, a })를 잡으려면 위치별 claim 레지스트리가 하나 필요하다는 방향은 확정됐고(Ref처럼 bindLifetime을 재사용할 수는 없음 — 그룹 값은 여러 곳에서 쓸 수 있어야 하므로), [2026-08-20 QA 4라운드] 이름도 groupClaimKeys로 확정. 남은 건 키를 무엇으로 할지((inst, groupValue) → k인지 groupKey 단위인지)와 기존 nameClaims와의 공존 방식 — base/attribute-plan.md의 "이름 소유권" 절.
  • [신설, 2026-08-18 구현 전 QA] 중간 State GC 미검증State → State → State → Observer 체인에서 중간 노드를 강하게 붙잡는 주체가 문서 어디에도 없어 전파가 조용히 끊길 수 있음. 방향(상류 strong / 하류 weak)은 사용자가 지목했고, 명문화 여부 결정 + luau-test 실측이 M3 착수 전에 필요base/source-state-plan.md의 "미해결 — 중간 State가 살아남는가" 절.
  • [신설, 2026-08-14 리뷰] AttributeGroupHandler.process의 부분 실패 롤백 — 이름 순회 도중 소유권 충돌 error가 나면 그 전에 등록된 이름들이 이 사이클엔 회수되지 않음(클로저가 안 만들어짐). 피해는 그 인스턴스 수명으로 한정되고 재현도 시끄럽게 반복돼서 지금은 별도 장치 없이 문서화만 했는데(base/attribute-plan.md "메커니즘" 절), 원자적 롤백(그룹 process에만 국소적인 unwind)을 넣을지는 열어둠. 지금 결정 불필요 — M10 구현 시점에 판단.
  • [해소됨, 2026-08-18] Attribute.Merged의 이름 중복Merged(겹치면 error)와 Overridden(겹치면 뒤가 이김)을 둘 다 제공하는 것으로 확정 (제3안). 근거·파급은 base/attribute-plan.md의 "채택안 — Tag와 동형인 array-part 값 객체" 절.
  • quad-debug 세부 API 이름research/debug-tooling-plan.md 참고. 채널 실현 가능성(BindableEvent/Function이 플러그인↔Play 중 게임 경계를 넘는지)까지 사용자가 Studio에서 직접 실측 검증 완료 — 기술적 불확실성은 다 해소됨, 남은 건 세부 API 이름뿐("이벤트 함수가 self로 instance를 읽는 게 quad 관습"이라는 언급은 2026-08-06 후속 세션에서 해소 — 채택 안 함으로 확정, base/event-plan.md "이벤트 핸들러는 self(Instance)를 받지 않는다" 절 참고). 사용자가 "quad 개발 완료 전엔 착수 못 함"으로 직접 후순위 지정한 건 여전함 — base 설계(M2 Dispatch/ M3 Source/M5 D 생성자) 시점에 훅 확장 지점만 고려해두면 됨.
  • 문서화 전략(UI 네이밍 컨벤션, Store 부작용을 게임 시스템에서 쓰는 패턴)research/documentation-plan.md(뼈대만). 정식 백로그 항목으로 올릴지, 착수 시점을 언제로 볼지 사용자 판단 필요.
  • v1 하위호환(compat) 레이어 — quad-roblox-v1-compatresearch/v1-compat-plan.md(신규, 2026-08-06, 두 차례 후속 논의로 수렴). 방향 확정: v1을 그대로 병행 실행 + 경계에서만 state:Observer()(lazy 포기)로 값을 리졸브해 v1 프로퍼티에 재대입하는 브리지, v2→v1 단방향만 (양방향 불필요로 확정), 패키지명 quad-roblox-v1-compat으로 확정(소스 트리에 세 번째 패키지로 추가될 예정). v2-in-v1/v1-in-v2 두 임베딩 방향 모두 기술적 근거와 안전 규칙까지 정리됐으나(문서 7번), Slot이 foreign Instance를 어떻게 다루는지만 Slot 코어 구현 시점까지 결정 불가로 남음 (위 "여러 Slot이 형제로 섞일 때 순서 보장" 항목과 같은 시점에 확인). 그 외 §8의 세부 항목(v1 자기 루트의 Destroying 자기청소 여부, registerClass 체이닝 기능 브릿징 필요성)은 문서 자체가 "지금 결정 불필요"로 표시해둠 — 위 Slot 항목과 별도로, 실제 compat 레이어 구현 시점에 research/v1-compat-plan.md §8을 다시 열어 확인.
  • Slot이 quad 밖에서 만들어진 임의 Instance를 받을 수 있는가 (2026-08-06 추가, 아직 안 풀림) — v1 compat 등에서 넘어온 foreign Instance를 동적 배열 원소로 받을 수 있는지, retract 시 어떻게 다루는지. Slot 코어 구현(M6) 시점에 확인research/v1-compat-plan.md 7-3.

없어진 번호에 대해: 예전 "0번(추가 프리미티브)"과 "2번(구현 착수 직전 감사 결과)"은 전원 해소되어 통째로 archive/question-resolved.md로 갔음. 우선순위1 11개의 개별 상태가 궁금하면 research/pre-implementation-audit.md가 원본이자 최신.


전체 순서/우선순위는 .claude/todos.md가 최종 소스. 확정된 것들의 문서 색인은 .claude/README.mdbase/ 표(예전에 이 문서 맨 아래에 있던 요약표는 그것과 중복이라 archive로 옮김).