quad/.claude/question.md
qwreey bcd02f1cec
docs(base): PostRef 확정·OnRendered 채택, PreRef/PostRef 계열 안 순서 미보장으로 역전
- base/ref-plan.md: "PostRef" 절 신설(PreRef의 거울상 — pre-pass 공동 수집,
  ProcessedPostRef 센티널+전담 Handler, 동적 경로 가드, _fired, 타입 차단).
  보장 범위를 명시: 자기 서브트리 완성은 보장하되 이 인스턴스가 부모에
  붙기 전에 불림(React componentDidMount와 다름).
- 복수 PreRef/PostRef의 계열 안 fire 순서를 "배열 index 순서 보장"에서
  미보장으로 역전 — 구현이 아니라 계약만 좁힘
  (archive/preref-order-guaranteed-reversed.md 신설).
- research/lifecycle-hooks-plan.md → base/ 승격, OnRendered 채택 반영
  (마지막 열린 항목이던 채택 여부/메커니즘/스코프/패키지 전부 확정).
- dispatch-core-plan/brand/architecture/slot/modifier/typing-limits/README/
  ROADMAP/question.md 전파. doc-check.py ERROR 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 05:45:31 +09:00

21 KiB

확인/결정 필요 목록

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

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


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

M0 착수를 막던 항목이 전부 해소됐습니다. 0-Y(:Compute(fn)의 lazy 핸들 계약)는 열세 번째 세션에, 0-Z(Attribute 이름 소유권)와 0-A(재디스패치 하강 diff)는 열네 번째 세션에 확정·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에서 분리 신설).

결정 대기 — M0는 안 막음

0-W. 같은 Ref 객체가 두 자리에 놓이는 걸 막을 것인가 (2026-08-13 열세 번째 세션 신설, 0-Z 확인 중 발견)

0-Z(Attribute, 열네 번째 세션에 해소됨)를 보다가 "Ref에도 같은 문제가 있냐"는 사용자 질문에서 나온 것 — 있고, 막는 장치가 전혀 없음. 단 메커니즘은 0-Z와 반대 방향이라 별개 항목으로 분리: Attribute는 두 소유자 → 한 자리(이름별 메모이즈된 키라 수렴), Ref는 한 객체 → 두 자리(발산).

손 트레이싱(base/ref-plan.mdRefLeafHandler 의사코드에 대입, Frame1 { Ref = r } / Frame2 { Ref = r }):

  1. process(inst1,"Ref",r)relate[inst1]["Ref"]가 nil → r:Set(inst1)
  2. process(inst2,"Ref",r)relate[inst2]["Ref"]도 nil(다른 키) → r:Set(inst2) — inst1 바인딩이 조용히 유실, 에러 없음
  3. inst1 자리가 retract → 클로저 인자 nil ~= v(r)r:Set(nil) — inst2가 정당하게 들고 있던 값을 지움(교차 오염)

relate(inst,k)별로만 있어 "이 Ref가 이미 다른 자리에 있다"를 원천적으로 못 봄.

형제 프리미티브 대조 — Ref만 비어 있음:

공유 자원 방어 상태
Slot element claimOwner/claimOwnerAt → 즉시 error(Slot{a,a}/Frame{slot,slot}) 막힘
PreRef/PostRef 자기 자신 _fired → 재사용 시 error 막힘 (PostRef는 2026-08-14 아홉 번째 세션 신설, 같은 가드 그대로)
Tag 태그 이름 위치별 참조 카운트 — 겹침이 의도된 동작(합집합) 설계상 정상
Attribute 이름 그룹 전용 키 + 이름 claim → 즉시 error 막힘(0-Z 해소, 14차 세션)
Ref 자기 자신 없음 이 항목

특히 걸리는 두 가지:

  • PreRef는 정확히 이 재사용을 error로 막고, 문서가 "Slot:ListupdateFn처럼 반복 호출되는 자리에선 매번 새 PreRef()를 만들라"는 관용구까지 명시해뒀음 — 일반 Ref는 같은 자리에서 같은 실수를 해도 아무도 안 막음(비대칭).
  • 스파이크 19가 Tag/Attribute/Slot 소유권은 음성 대조군까지 넣어 검증하는데 Ref만 커버가 없음.

0-Z와의 관계 — 독립: 이건 하강 diff 모델이 만든 회귀가 아니라 원래부터 있던 갭(옛 점유 체크도 같은 (inst,k,index)만 봤지, 서로 다른 자리를 가로지르는 건 원래 안 봤음). [2026-08-13 열네 번째 세션] 0-Z가 해소되면서 이 표에서 Ref만 유일하게 비어 있게 됐음 — Attribute는 "이름 claim"이라는 국소 레지스트리로 갔으니, Ref도 같은 모양(Ref → 현재 자리 단방향 Relate + 즉시 error)이 자연스러운 선택지. 여전히 M0를 막지는 않음.

선택지: (a) Slot/PreRef와 같이 즉시 error(일관성 높음, Relate 하나로 Ref→현재 자리 추적), (b) UB로 두고 문서화만(현상 유지 — 단 증상이 "조용한 값 소실"이라 다른 UB보다 나쁨), (c) 마지막 쓰기 승리를 정식 동작으로 인정(비권장, Ref의 "확정된 값 박스" 의미와 충돌).

0-B. dispose(any) — 시그니처/범위 (2026-08-13 여섯 번째 세션 신설, 사용자 제안)

State<Slot> 교체를 파괴가 아니라 언마운트로 확정하면서(state<Frame>와 동일, base/slot-plan.md), 명시적 파괴 수단으로 base 탑레벨 dispose(value)를 제공하기로 방향 확정. "이 값이 지금 어디 마운트돼 있는가"는 이미 elementOwner가 들고 있어(다중 마운트 error 판정용) 새 부기가 필요 없음.

[확정, 사용자] 시맨틱은 "거부" — 대상이 아직 어느 트리에 의해 살아있길 요구되고 있으면 파괴를 거부하고 즉시 error. 떼어내주지 않음(떼어내는 건 Set=언마운트의 몫, dispose는 그 뒤). 근거: 엔진은 Destroy/Clear에 에러를 안 내지만 quad의 _elements/lengthList/ sourceList/elementOwner는 그 순간 어긋나므로, quad가 관리 중인 값을 안전하게 지우는 유일한 경로가 이것이고 "지금 지우면 안 되는 상태"를 잡아주는 게 존재 이유. 이걸로 "Set 전에 직접 Destroy()"가 UB에서 명확한 에러로 바뀜.

미확정: 시그니처(dispose(any)가 맞는지, 타입을 어떻게 좁힐지), 대상 범위(Slot 외에 Instance/Observer/Effect까지 커버하는지), unbindLifetime과의 역할 분담.

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

사용자 원 메모: "quad는 register라던가 좀 부정확하거나 느낌이 바로 와닿지 않던 용어들이 많음 — 전체적 용어를 보고 생각해볼래? 제안을 줘, 나도 같이 볼게." 이미 확정된 이름(State/Relate/List/Ref/ PreRef/Peek/isState/None/NoneHandler/Handler)의 근거는 archive/question-resolved.md. (canBound는 2026-08-14 다섯 번째 세션에 폐기되어 이 목록에서 빠짐 — canExecute 하나로 통합됐음, archive/canexecute-inst-arg-reversed.md.)

  • DI(Declarative Instance, 1순위): "Dependency Injection"의 업계 표준 축약어와 완전히 겹침 — 4차 라운드에서 이미 한 번 실제로 오해가 있었던 전례(base/bind-system-plan.md의 "인스턴스 생성" 절 참고). 파급 효과(2026-08-06 추가): DI가 리네임되면 DI.FrameModifier류 Modifier 클래스별 타입 프리픽스도 같이 바뀌어야 함 — DI 리네임 논의 때 이 연쇄까지 같이 고려할 것. (2026-08-08 추가) 사용자가 D(Declarative 만 남김)로 축약하는 안을 제안 — 근거: (1) "Instance" 전용 개념이 아니라 quad-* 전반의 declare 요소로 확장해도 되는 이름, (2) 엔진 종속 없이 다른 백엔드에서도 재사용 가능, (3) 어차피 D.FrameModifier류 타입 프리픽스가 길면 못 쓰므로 짧아야 한다는 실용적 제약. 아직 최종 확정 아님 — 다음 세션에서 마저 논의(한 글자 식별자의 검색성/자기설명력 트레이드오프를 문서에서 어떻게 보완할지도 같이).
  • 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-인자는 폐기), 폐기된 canBound의 몫까지 이 하나가 겸함(base/lifecycle-pattern.md, archive/canexecute-inst-arg-reversed.md) — 이름을 바꾸면 그 두 역할을 다 담아야 함에 유의.
  • 클로저 인자 이름 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(...)" 절 참고.
  • OnDestroyed(3순위, 사소함, 2026-08-14 아홉 번째 세션 추가): base/lifecycle-hooks-plan.md가 이 이름으로 확정하되, 위 0-B (dispose(any) — 시그니처/범위)가 "quad가 만드는 모든 것의 유일한 파괴 경로"로 풀리면 OnDisposed와 맞추는 재검토 여지를 남겨둠. 지금 OnDestroyed인 이유는 실제 트리거가 dispose() 호출이 아니라 엔진 Destroying 신호라서(그 문서 "이름 컨벤션" 절). 이름은 런타임에 아무 의미가 없는 순수 네이밍이라 바꾸는 비용이 0에 가까움 — 0-B가 풀리기 전엔 이 항목을 열지 말 것(형제 OnCreated/OnRendered는 재검토 대상 아님, 다만 OnRendered엔 "부모에 붙기 전에 불린다"는 캐비엇이 있어 이름이 아니라 문서화로 대응하기로 확정됨).
  • 참고 — 이미 지나간 사례: register(v1) → State(v2) 리네임은 "모호함"은 풀었지만 "다른 뜻으로 이미 쓰이는 단어"라는 새 문제를 만든 셈 — 이번 정리에서 같은 패턴을 조심할 것.
  • Store/Source/Modifier/process/retract/isHandlable은 업계 선례와 잘 맞거나 이미 신중하게 결정된 이름들이라 특별한 문제 없음.

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

  • 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와는 다른 시간 기반 메커니즘이라 이 카탈로그가 아니라 quad-roblox 쪽 별도 프리미티브로 다룰지 판단이 필요한 별개 질문으로 분리됨. [2026-08-13 세션 신설] Alternative(nil 대체값, coalesce/??/ 엘비스 연산자류) 후보 추가 — Haskell 비교 리서치 중 나옴, 카탈로그 확정 규칙에 그대로 맞아 포함 근거는 있음. 상세는 research/operator-sugar-plan.md. 구현 자체는 맨 마지막 우선순위(순수 슈가, 없어도 무방) — 여전함.
  • [신설, 2026-08-14] Debounce/Throttle — 남은 열린 질문 4개 — 사용자 요청("Blocker와 유사하게 만들어야 한다")으로 research/debounce-throttle-plan.md 신설. 네 번의 리뷰 라운드로 패키지 경계·주입 op 시그니처·Timeout 타입·동작 정식화는 전부 확정됐고, 사용자 취향/방향이 갈리는 것만 남음:
    • 의미론 — 창이 열려 있는 동안 :Get()이 최신값을 주는가 (Blocker와 동일) vs 지연된 값을 주는가(권장, VueUse/RxJS와 일치).
    • 제어 핸들state:Apply(Debounce{...})로 끝낼지, Blocker와 글자 그대로 같은 모양(Debouncer 객체 + state:Debounce(d) + d:Flush()/:Cancel())으로 갈지. "검색창에서 Enter 누르면 즉시 커밋"류가 잦다고 보면 후자가 처음부터 나음.
    • 이름 — Roblox 관용 "debounce"(재진입 방지 불리언)와 정면 충돌. 업계 표준 이름을 유지하고 문서로 경고할지, 다른 이름을 쓸지.
    • Time = 0 허용 여부 — "이번 스텝 합치기"로 유용하지만 스케줄러 타이밍에 의존하는 준-Blocker가 됨. 우선순위 낮음 — M0를 막지 않고 코어 계약도 안 건드림. 다만 M3에서 Blocker를 구현할 때 게이티드 노드를 공용으로 빼두는 것만은 그 시점에 해야 함(따로 하면 같은 설계를 두 번 함).
  • 중첩 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-14 리뷰] AttributeGroupHandler.process의 부분 실패 롤백 — 이름 순회 도중 소유권 충돌 error가 나면 그 전에 등록된 이름들이 이 사이클엔 회수되지 않음(클로저가 안 만들어짐). 피해는 그 인스턴스 수명으로 한정되고 재현도 시끄럽게 반복돼서 지금은 별도 장치 없이 문서화만 했는데(base/attribute-plan.md "메커니즘" 절), 원자적 롤백(그룹 process에만 국소적인 unwind)을 넣을지는 열어둠. 지금 결정 불필요 — M10 구현 시점에 판단.
  • [신설, 2026-08-13 열네 번째 세션] Attribute.Merged의 이름 중복 — 두 Store가 같은 이름을 가지면 지금은 :NameMap() 평탄화 단계에서 조용히 하나가 이김(dispatch 이전이라 이름 claim이 못 잡는 자리). 합성 시점 1회 체크로 error를 내는 게 이 문서 다른 결정들과 결이 같지만, "Merged는 뒤가 이긴다"를 의도된 override로 볼 여지도 있어 사용자 확인 필요 — base/attribute-plan.md "열린 질문" 절.
  • 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 DI 생성자) 시점에 훅 확장 지점만 고려해두면 됨.
  • 문서화 전략(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.md가 최종 소스. 확정된 것들의 문서 색인은 .claude/README.mdbase/ 표(예전에 이 문서 맨 아래에 있던 요약표는 그것과 중복이라 archive로 옮김).