quad/.claude/session/2026-08-08-02-dispatch-singleton-naming.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

7.3 KiB

2026-08-08 두 번째 세션 — Dispatch는 프리미티브가 아니라 탑레벨 싱글톤 확정,

네이밍 케이싱 컨벤션 신설, Handler를 세 번째 카테고리로 명문화

같은 날 이어진 세션. 사용자가 위 4번 미결 항목("Ref/Observer/PreRef leaf Handler가 어디 사는지")을 다시 짚으며 시작 — "Handler도 실제 런타임 값이 생기는 요소인데 왜 프리미티브로 안 다루나", "Dispatch는 어떻게 되는 거냐, State 핸들러 안에서 getHandler를 부르려면 Dispatch가 이미 존재해야 하는데" 하는 질문으로 확장돼 Dispatch 자체의 정체성(싱글톤 top-level 함수 모음 vs 인스턴스화 가능한 프리미티브) 논의로 이어짐. 네 가지로 정리, 전부 base/bind-system-plan.md/base/store-semantics.md/ base/architecture.md/question.md/ROADMAP.md에 반영 완료:

1. Dispatch는 프리미티브가 아니라 탑레벨 싱글톤 — 확정, 지금 형태 유지. Dispatch.process/getHandler/addHandler/driveSource/Ref처럼 생성자 있는 프리미티브로 바꿀지 검토했으나 기각. 근거: (a) Tween/ NoneHandler/StoreBind가 자기 process 안에서 다시 Dispatch.process를 재귀 호출해야 해서, canExecute/bindLifetime처럼 require 한 번으로 바로 닿는 안정된 전역이어야 함 — 프리미티브화하면 모든 Handler 호출 경로에 Dispatch 핸들을 실어날라야 하는 스레딩 비용이 생기는데 지금은 그 비용이 없음. (b) 사용자가 우려한 "Handler가 Dispatch 원하고 Dispatch가 Handler 원해서 순환참조" 문제는 착시로 확인됨 — "Handler"가 (i) Handler.luau의 순수 타입 계약(leaf, Dispatch를 몰라도 됨)과 (ii) 그 계약을 구현하는 concrete 값 모듈(StoreBind.luau류, 재귀호출 위해 Dispatch를 참조) 두 가지를 가리켜서 헷갈렸던 것 — 의존 방향은 Handler.luauDispatch/init.luauStoreBind.luau로 항상 한쪽으로만 흐름, 사이클 없음. (c) 모듈 재생성(New())과의 관계도 새 설계가 필요 없음 — 이미 확정된 "팩토리가 BaseModule을 뮤테이션" 패턴을 그대로 따르면 _initializedBy 마커에 대해 이미 나왔던 결론("New()가 생기면 각 인스턴스가 별도 테이블이 되므로 자연히 스코핑됨")이 Dispatch의 handler 레지스트리에도 그대로 적용됨. v1처럼 require를 감싸는 Init(QuadId?) 방식은 채택 안 함(id 기반 조회 자체가 Ref로 대체되며 이미 기각된 패턴). base/bind-system-plan.md의 "Dispatch는 프리미티브가 아니다" 절, base/architecture.md 13번 항목에 반영.

2. quad-base 기본 핸들러도 전부 같은 Dispatch.addHandler 레지스트리를 공유 — Ref/Observer/PreRef leaf Handler 위치 확정. NoneHandler/ Dispatch/StoreBind.luau뿐 아니라, children 배열 숫자 슬롯에 Ref/ Observer/PreRef를 직접 놓는 leaf 값을 매칭하는 Handler도 같은 부류 — instany로 취급하고 엔진 특정 API가 필요 없으니 quad-base, Dispatch/Leaf.luau로 확정(위 4번 미결 항목 해소). quad-roblox의 Property/Event/Tween 핸들러도 같은 레지스트리에 등록되므로, base 기본 핸들러와 backend 핸들러가 별도 경로로 안 갈리고 하나의 우선순위 스캔을 공유한다는 것도 명시적으로 확인됨. architecture.md 소스트리에 Dispatch/Leaf.luau 반영, question.md/ROADMAP.md M2 동기화.

3. Handler는 "독립 프리미티브 vs 파생 데이터" 분류의 세 번째, 별개 카테고리 — 명문화. 2026-08-06 후속 세션이 확정한 분류(Source/Ref/Store/ Modifier=독립 프리미티브, State/Observer=파생 데이터)에 Handler가 왜 안 끼는지 사용자가 재확인 요청 — 이유: Handler는 그 자체로 구현체가 없는 순수 타입 계약이라 quad 사용자가 다루는 리액티브 값이 아님, 계약을 만족하는 값(PropertyHandler류)은 항상 구현하는 쪽(base 자신의 기본 핸들러 또는 quad-roblox 백엔드)이 채워 넣는 것이지 Type(args) 자유 함수로 사용자가 만드는 게 아니고, State/Observer처럼 어떤 원천에 종속된 파생물도 아님. base/store-semantics.md의 "일반 원칙" 절 뒤에 "세 번째 카테고리 — Handler" 절로 반영.

4. 네이밍 케이싱 컨벤션 신설 — 지금까지 나온 모든 이름이 이미 따르고 있던 규칙을 문서화만 함, 리네임 없음. 사용자 관찰: "탑레벨 함수는 변수처럼 소문자 시작, 프리미티브 타입의 메서드는 대문자 시작(파스칼 케이싱)이 맞아 보인다"는 규칙 제안 — 검증 결과 기존 이름 전체(생성자 Source/Ref/Store/Modifier/Relate/Effect, 콜론 메서드 :Get/:With/:Set/:Apply/:Subscribe류는 전부 대문자, canExecute/ bindLifetime/isState류/Dispatch.process류/Brand.set류는 전부 소문자)가 이미 예외 없이 이 규칙을 따르고 있었음이 확인됨. 유일하게 애매해 보였던 Modifier.Override(mod1, mod2, ...)(콜론 아니고 dot-access 인데 대문자)도 규칙 위반이 아니라 세 번째 하위 규칙으로 설명됨 — 콜론 메서드는 아니지만 Modifier 타입 자신의 네임스페이스에 달린 정적 결합 함수라 "그 프리미티브 타입 고유의 공개 어휘"라는 점에서 생성자/ 메서드와 같은 부류. 반대로 Dispatch.process/Brand.set이 소문자인 이유는 Dispatch/Brand가 애초에 Type(args) 생성자가 없는 프리미티브가 아닌 내부 엔진/레지스트리라서. 최종 판단 기준: "이 이름이 특정 프리미티브 타입 하나의 전용 소유물인가?" — 그렇다면 대문자, 아니면(여러 타입에 걸친 범용 유틸이거나 비-프리미티브 엔진 소속) 소문자. base/ architecture.md의 "코드 스타일 — 네이밍 케이싱" 절 신설.

같은 세션 후속 — module-lifecycle-plan.md의 "열린 질문" 절이 stale로 방치돼 있던 것을 사용자가 직접 발견. 문서 상단 "상태" 줄은 이미 "확정되어 승격됨"이라고 말하는데 그 아래 "열린 질문" 절은 2026-08-04 당시 그대로 남아있었음 — 그중 "프로바이더 인터페이스 시그니처 미정"/ "네이밍 미정(provider/processor/plug)" 두 항목이 사실 그 뒤 Handler 계약 확정으로 이미 풀려 있었는데 이 문서에 반영이 안 됐던 것. 원문은 남기고 각 항목에 해소 표시+포인터 추가, 절 제목도 "열린 질문이었던 것 — 전부 해소됨"으로 정정. 새 결정 아니라 순수 동기화.

다음 세션이 할 일: 안 바뀜(ROADMAP.md M0부터) — 이번 세션도 순수 설계/문서 정리라 M0 착수 우선순위 자체는 그대로. 위 2026-08-08 첫 세션이 남긴 "M0/M2 스파이크 검증 목록"에 새로 추가되는 항목 없음.