diff --git a/.claude/base/architecture.md b/.claude/base/architecture.md index c7f0ef3..5b8662c 100644 --- a/.claude/base/architecture.md +++ b/.claude/base/architecture.md @@ -85,7 +85,14 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo `quad-roblox`가 담당. 13. **모듈은 기본 싱글톤, `New()`는 나중에.** 한 Lua 스레드에서 Roblox/비-Roblox 프로바이더를 동시에 쓸 일이 거의 없을 거라 판단 — 필요해지면 그때 `New()` - 추가. + 추가. **메커니즘도 이미 정해짐(2026-08-08 두 번째 세션, 새 설계 아니라 + 기존 패턴의 자연스러운 연장)**: v1처럼 `require`를 감싸 `Init(QuadId?)`로 + 격리 인스턴스를 만드는 방식은 안 씀 — 대신 지금 있는 "팩토리가 + `BaseModule`을 뮤테이션" 패턴(14번) 그대로, `New()`가 생기면 매번 새 + `BaseModule` 테이블을 만들어 팩토리로 채우는 것뿐. Dispatch의 handler + 레지스트리를 포함해 지금 module-level state로 사는 모든 것(`_initializedBy` + 마커, Dispatch 레지스트리 등)이 자동으로 테이블별 스코핑됨 — 상세 근거는 + `base/bind-system-plan.md`의 "Dispatch는 프리미티브가 아니다" 절. 14. **pluggable 초기화는 팩토리 함수로.** rbvm처럼 네임스페이스 하나하나 수동 init 하는 방식(`base/lifecycle-pattern.md` 5번 항목 참고)은 피하고, `InitRoblox(Module)` 같은 팩토리 함수가 생성된 모듈을 뮤테이션하는 도구를 @@ -132,6 +139,7 @@ quad/ │ │ ├── init.luau # process/retract 엔진, isHandlable 우선순위 스캔 │ │ ├── Handler.luau # 핸들러 계약 타입(isHandlable/priority/process/retract) │ │ ├── StoreBind.luau # store 값 재귀 재실행 로직(범용, 엔진 무관) +│ │ ├── Leaf.luau # (i:number, v=Ref/Observer/PreRef) children-array leaf 매칭 Handler, StoreBind와 같은 층위(범용/엔진무관, 2026-08-08 두 번째 세션 확정) │ │ └── Slot.luau # add/remove/clear 재조정 로직(추상 자식 참조 기준) │ ├── Relate.luau # inst를 weak 키로 하는 범용 릴레이션(`SetWeak`/`GetWeak`/`SetStrong`/`GetStrong`), 비싱글톤 생성자(`base/relate-plan.md`) — 구 PerInstanceState/perInstanceState 대체 │ ├── LifetimeHandle.luau # `bindLifetime(inst,value)`/`canExecute(inst,value)` 탑레벨 함수 "인터페이스"(타입/계약만), 내부는 Relate 사용(`base/lifecycle-pattern.md`) @@ -161,6 +169,45 @@ quad/ Tween/existing-instance-bind는 여전히 `research/`에 남아있고 이 구조 확정을 막지 않음(`purity-and-effects-plan.md`는 이미 `base/`로 승격 완료). +## 코드 스타일 — 네이밍 케이싱 (2026-08-08 두 번째 세션 신설) + +지금까지 각 문서가 예시 코드를 쓰며 암묵적으로 따라온 패턴을 사용자가 +명시적 규칙으로 정리해달라고 요청 — 실제로 지금까지 나온 모든 이름이 +예외 없이 따르는 규칙이라 새로 뭘 바꿀 필요는 없고, 그냥 문서화만: + +- **대문자 시작(PascalCase)** — 다음 세 가지, 공통점은 전부 **어떤 + 프리미티브 타입 자신의 공개 어휘**라는 것: + 1. 프리미티브 타입 생성자, `Type(args)` 스타일: `Source(default)`/ + `Ref(default)`/`Store({defaults})`/`Modifier()`/`Relate()`/ + `Effect(fn, state?)`/`PreRef(default)`. + 2. 그 인스턴스의 콜론 메서드: `state:Get()`/`:With(...)`/`:Compute(fn)`/ + `:Observer(fn)`/`:Apply(factory)`/`:Peek(key)`, `source:Set(v)`/`:Emit()`, + `ref:Set(v)`/`:Callback(fn)`/`:Wait(thread?)`, `observer:Subscribe()`/ + `:Unsubscribe()`, `relate:SetWeak(...)`/`:GetWeak(...)`/`:SetStrong(...)`/ + `:GetStrong(...)`, `mod:FontSize(...)`(필드 setter 체이닝). + 3. 프리미티브 타입 자신의 네임스페이스에 달린 정적 결합 함수 — + `Modifier.Override(mod1, mod2, ...)`가 유일한 현재 사례. 콜론 메서드는 + 아니지만(여러 Modifier를 동등한 인자로 받아야 해서 self 하나로 안 + 됨) `Modifier` 타입 고유의 공개 연산이라는 점에서 1/2과 같은 부류 — + `Modifier()` 생성자와 같은 이유로 대문자. +- **소문자 시작(camelCase)** — 특정 프리미티브 타입 하나에 안 묶이고 여러 + 타입을 넘나드는 범용 유틸(`isState`/`isSource`/`isRef`/`isPreRef`/ + `isModifier`/`isObserver`/... `Brand` 절), 생명주기 게이트(`canExecute`/ + `bindLifetime`, `base/lifecycle-pattern.md`), 그리고 **프리미티브가 + 아닌** 내부 엔진/레지스트리의 네임스페이스 멤버(`Dispatch.process`/ + `getHandler`/`addHandler`/`drive`, `Brand.set`/`get`) — 이 셋은 "타입 + 고유의 어휘"가 아니라 여러 타입에 걸쳐 쓰이거나(`isX`류) 프리미티브 + 자체가 아닌 것(Dispatch/Brand는 `Type(args)` 생성자가 없는 내부 엔진)의 + 구성원이라 PascalCase 대상이 아님. Handler 계약 필드(`isHandlable`/ + `priority`/`process`/`retract`)도 여기 속함 — 이건 애초에 "함수"라기보다 + 구현체가 채워 넣는 구조체 필드. +- **경계 판단 기준**: 새 이름을 지을 때 "이게 특정 프리미티브 타입 하나의 + 전용 소유물인가?"로 물으면 됨 — 그렇다면 대문자(생성자/메서드/그 + 타입의 정적 결합 함수), 아니면(범용 유틸이거나 프리미티브가 아닌 엔진 + 소속) 소문자. `Dispatch`/`Brand`가 프리미티브가 아닌 이유는 + `base/bind-system-plan.md`의 "Dispatch는 프리미티브가 아니다" 절/ + `base/store-semantics.md`의 "세 번째 카테고리 — Handler" 절 참고. + ## 테스트 전략: quad-base용 최소 mock (2026-08-04) **결정**: quad-base 테스트는 Vide 선례(`initreq/vide/test/mock.luau`, 약 diff --git a/.claude/base/bind-system-plan.md b/.claude/base/bind-system-plan.md index d1cae34..06cd5c5 100644 --- a/.claude/base/bind-system-plan.md +++ b/.claude/base/bind-system-plan.md @@ -262,6 +262,57 @@ NoneHandler.process(inst, k, v) = process(inst, k, nil) -- 재귀 재호출 통해 담당자 기록을 갱신하게만 해두면(`Dispatch.drive`가 별도로 기록 안 하고 `Dispatch.process` 내부에 위임) 자연히 해소됨. +### Dispatch는 프리미티브가 아니다 — 탑레벨 싱글톤 확정 (2026-08-08 두 번째 세션) + +`Dispatch.process`/`getHandler`/`addHandler`/`drive`를 `Source`/`Ref`/`Store`/ +`Modifier`처럼 생성자가 있는 프리미티브(예: `Dispatch()`로 인스턴스를 여러 개 +만들 수 있는 것)로 바꿔야 하는지 검토 후 **기각, 지금 형태(모듈 require로 +바로 닿는 flat 탑레벨 함수) 유지로 확정**: + +- **재귀 재-dispatch가 요구하는 필연** — Tween/`NoneHandler`/`Dispatch/ + StoreBind.luau` 전부 자기 `process` 안에서 다시 `Dispatch.process(inst,k, + realv)`를 호출함(위 "확정된 디스패치 모델"/"`None` 센티널" 절). 이게 + 성립하려면 Dispatch가 `canExecute`/`bindLifetime`(`base/ + lifecycle-pattern.md`)과 똑같이 require 한 번으로 바로 닿는 안정된 + 전역이어야 함 — 인스턴스화 가능한 프리미티브로 만들면 모든 Handler + 등록/호출 경로에 Dispatch 핸들을 인자로 계속 실어날라야 하는 스레딩 + 비용이 생기는데, 지금 형태는 그 비용을 아예 안 짐. +- **순환참조로 보이는 건 착시 — 실제로는 단방향.** "Handler"라는 말이 두 + 가지를 가리켜서 헷갈릴 수 있음: (a) `Handler.luau`의 **타입 계약** + (`isHandlable`/`priority`/`process`/`retract` 시그니처만 있는 순수 leaf, + Dispatch를 몰라도 됨) vs (b) `StoreBind.luau`/`Tween.luau`처럼 그 계약을 + **구현하는 concrete 값 모듈**(재귀호출 위해 Dispatch를 require함). 의존 + 방향은 항상 한쪽으로만 흐름 — `Handler.luau`(leaf) ← `Dispatch/init.luau` + (`addHandler(h: Handler)`가 `Handler` 타입만 참조) ← `StoreBind.luau`/ + `Tween.luau`(재귀호출 위해 Dispatch를 참조). `Handler.luau` 자신이 + Dispatch를 되받아 참조하는 일이 없으니 타입 레벨에서도 사이클이 안 생김. + 런타임에서도 마찬가지 — 어떤 handler의 `process`든 실제로 *호출*되는 + 시점은 컴포넌트가 렌더되는 시점이라, 그때는 이미 Dispatch 모듈 require가 + 완전히 끝나있어 부트스트랩 문제도 없음. +- **quad-base 자신의 기본 핸들러도 같은 레지스트리를 씀** — `NoneHandler`, + `Dispatch/StoreBind.luau`("범용, 엔진 무관")뿐 아니라, children 배열 + 숫자 슬롯에 `Ref`/`Observer`/`PreRef`를 직접 놓는 leaf 값을 매칭하는 + Handler도 여기 속함(`inst`를 `any`로 취급, 엔진 특정 API 불필요 — + `.claude/question.md`가 2026-08-08 세션에 "quad-base/quad-roblox 중 + 어디 사는지 미확인"으로 남겨뒀던 항목, 이 결론으로 해소: quad-base, + `Dispatch/Leaf.luau`, `Dispatch.addHandler`로 등록). quad-roblox의 + Property/Event/Tween 핸들러도 **같은** `Dispatch.addHandler` 레지스트리에 + 등록됨 — base 기본 핸들러와 backend 핸들러가 별도 경로로 안 갈리고 + 전부 하나의 우선순위 스캔을 공유. +- **모듈 재생성(`New()`)과의 관계 — 새 설계 불필요, 이미 있는 선례로 자연히 + 풀림.** v1처럼 `require`를 감싸 `Init(QuadId?)`로 격리 인스턴스를 만드는 + 방식은 안 씀(위 "확정된 것" 절 — id 기반 조회 자체가 Ref로 대체되며 + 기각됨). 대신 이미 확정된 "base 유틸은 인터페이스, 실제 구현은 팩토리가 + `BaseModule`을 뮤테이션해서 주입"(`RobloxFactory(BaseModule)`) 패턴을 + 그대로 따름 — Dispatch의 handler 레지스트리도 `BaseModule` 테이블에 + 딸린 state 중 하나일 뿐이라, `_initializedBy` 마커에 대해 이미 확정된 + 것과 완전히 같은 논리가 적용됨(위 "base 유틸은 인터페이스" 절, "`New()`가 + 생기면 각 인스턴스가 별도 테이블이 되므로 이 마커도 테이블별로 독립적으로 + 스코핑됨, 재설계 불필요"). `New()`가 실제로 생기면 그 시점에 BaseModule + 전체를 인스턴스별 테이블로 만드는 메커니즘에 Dispatch도 자연히 같이 + 딸려가고, 호출부는 `module.Dispatch.process(...)`처럼 그 인스턴스 + 테이블을 통해 접근하게 됨 — 지금 미리 프리미티브화해둘 이유가 없음. + ## Store 바인드는 특수 경우인가, 아니면 pluggable 바인드를 재실행하는 래핑인가 사용자 원 메모: "스토어 바인드는 특수 경우로 둘지, 아니면 다른 pluggable 바인드를 diff --git a/.claude/base/store-semantics.md b/.claude/base/store-semantics.md index 58ff539..c493e3c 100644 --- a/.claude/base/store-semantics.md +++ b/.claude/base/store-semantics.md @@ -90,6 +90,22 @@ pull-recompute)·`:Compute` 인자 규칙·State 쓰기 금지·`Source` 독립 속하는지가 생성자 모양(자유 함수 팩토리 vs 원천에 대한 메소드)을 결정하는 기준으로 쓸 수 있음. +**세 번째 카테고리 — Handler는 둘 중 어디에도 안 낌(2026-08-08 두 번째 +세션, 명시화).** `Handler`(`isHandlable`/`priority`/`process`/`retract` +4종 계약, `base/bind-system-plan.md` "핸들러 계약" 절)는 위 분류가 다루는 +"quad 사용자가 직접 다루는 리액티브 값"이 아니라 **그 자체로는 구현체가 +없는 순수 타입 계약**이라 애초에 이 분류표의 대상이 아님 — Source/Ref처럼 +`Type(args)` 자유 함수로 인스턴스를 만들 수도 없고(계약을 만족하는 값은 +`PropertyHandler`/`TagHandler`/`Dispatch/StoreBind.luau`의 `NoneHandler`처럼 +**구현하는 쪽**이 리터럴 테이블로 직접 채워 넣는 것), State/Observer처럼 +어떤 원천에 종속된 파생물도 아님(애초에 "원천"이라는 개념 자체가 안 맞음). +Handler는 quad 사용자가 아니라 **백엔드/핸들러 구현자가 채우는 확장 +지점**이라는 완전히 다른 축의 개념이라, 여기 분류를 "왜 Handler가 +빠졌는지" 궁금해할 필요 없음 — 프리미티브 분류가 불완전한 게 아니라 +Handler가 애초에 다른 층위. 관련해서 Handler를 담는 엔진(`Dispatch`) 자체가 +왜 프리미티브가 아니라 탑레벨 싱글톤인지는 `base/bind-system-plan.md`의 +"Dispatch는 프리미티브가 아니다" 절 참고. + 과거 "미해결로 남은 것"으로 적었던 두 항목도 모두 해소됨: `:Compute`의 캐싱/무효화 전략은 push-invalidate(신호만)/pull-recompute(`Get()` 시점)로 확정(`base/bind-system-plan.md` "전파 모델" 절), `store "key"` 커링의 타입 diff --git a/.claude/question.md b/.claude/question.md index ad8780a..03a2824 100644 --- a/.claude/question.md +++ b/.claude/question.md @@ -156,17 +156,14 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만** 우선순위 스캔 동률/매치실패 처리, `:Compute`의 `previous` 인자가 오버엔지니어링일 수 있음, UI shorthand의 기존 UICorner 매칭 기준 등)는 `pre-implementation-audit.md` 본문 참고. -- **[신규, 2026-08-08 세션, 미확인]** `Frame { ref }`/`Frame { observer }`처럼 - children 배열 숫자 슬롯에 직접 놓는 leaf 값을 실제로 매칭·바인드하는 - Handler(`(i:number, v=Ref/Observer/PreRef)`)가 어느 패키지에 사는지 — - `base/bind-system-plan.md:301-303,852-854`는 "`(v=Ref)` 매치 핸들러가 - 처리한다"/"`isObserver`로 판별해 라이프사이클에 묶어준다"까지만 서술하고 - 파일 배치는 안 함(`architecture.md` 소스트리에도 이 Handler가 이름으로 - 안 나와 있음). 제안(미확정): `Ref`/`Observer`/`PreRef` 전부 `inst`를 `any`로 - 취급하는 engine-agnostic 타입이고 process가 `v:Set(inst)`/`LifetimeHandle` - 위임 정도만 하면 되니, `Dispatch/StoreBind.luau`(이미 "범용, 엔진 무관"으로 - 분류)와 같은 층위로 `quad-base`에 두는 게 맞아 보임 — `quad-roblox/Handlers/`가 - 아니라. 사용자 확인 필요, 아직 base에 반영 안 함. +- **[해소됨, 2026-08-08 두 번째 세션]** `Frame { ref }`/`Frame { observer }`처럼 + children 배열 숫자 슬롯에 직접 놓는 leaf 값을 매칭·바인드하는 Handler + (`(i:number, v=Ref/Observer/PreRef)`)의 패키지 배치 — 원래 제안대로 + `quad-base`, `Dispatch/Leaf.luau`(이미 있던 `Dispatch/StoreBind.luau`와 + 같은 층위)로 확정. Dispatch 자체가 프리미티브가 아니라 탑레벨 싱글톤이고 + base 기본 핸들러와 quad-roblox 백엔드 핸들러가 같은 `Dispatch.addHandler` + 레지스트리를 공유한다는 결론과 함께 나온 것 — `base/bind-system-plan.md` + "Dispatch는 프리미티브가 아니다" 절, `base/architecture.md` 소스트리 참고. ### 3. 낮은 우선순위 diff --git a/CLAUDE.md b/CLAUDE.md index 3083ada..e47c0fe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1443,10 +1443,90 @@ leaf Handler가 quad-base/quad-roblox 중 어디 사는지.** 3번을 풀다가 내부 Observer와는 무관) — 제 제안(엔진 특정 API가 필요 없으니 quad-base, `Dispatch/StoreBind.luau`와 같은 층위)은 사용자 확인을 못 받은 채 대화가 3번으로 넘어감. `question.md` 2번에 미확인으로 남김, base에는 반영 안 함 -— 다음에 확인 필요. +— 다음에 확인 필요. **[해소됨, 같은 날 두 번째 세션]** 아래 절 참고 — +제 원래 제안 그대로 quad-base로 확정. **다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터). M0/M2 스파이크 코드가 검증해야 할 것 목록에 `Relate`의 lazy 서브테이블 생성/공유 메타테이블 전략, `bindLifetime`/`canExecute`의 실제 gcconn 트릭이 새로 추가됨 — `base/lifecycle-pattern.md`/`base/relate-plan.md`의 "실측 필요" 캐비엇 -참고. 4번(Ref/Observer leaf Handler 위치)도 M2 착수 전 확인 대상. +참고. + +## 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`/`drive`를 `Source`/`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.luau` ← +`Dispatch/init.luau` ← `StoreBind.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도 같은 부류 — +`inst`를 `any`로 취급하고 엔진 특정 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`의 "코드 스타일 — 네이밍 케이싱" 절 신설. + +**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터) — 이번 세션도 순수 +설계/문서 정리라 M0 착수 우선순위 자체는 그대로. 위 2026-08-08 첫 세션이 +남긴 "M0/M2 스파이크 검증 목록"에 새로 추가되는 항목 없음. diff --git a/ROADMAP.md b/ROADMAP.md index ed7e5ac..1449589 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -109,6 +109,10 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 걸러내기(no-op이라도 필드 자체는 항상 정의 — `Dispatch.process`가 핸들러 교체 시 nil 체크 없이 호출, `base/bind-system-plan.md` "핸들러 계약" 절, 2026-08-08 세션) +- [ ] `Dispatch/Leaf.luau` — `(i:number, v=Ref/Observer/PreRef)` children-array + leaf 매칭 Handler, `StoreBind.luau`와 같은 층위(범용/엔진무관) — + quad-base 소속으로 확정(2026-08-08 두 번째 세션, `base/ + bind-system-plan.md` "Dispatch는 프리미티브가 아니다" 절) - [ ] mock 대상 테스트 ## M3 — Store/State/Source