From 1a612ecfa0ad21d50bb850c5616a937936876b7f Mon Sep 17 00:00:00 2001 From: qwreey Date: Sat, 8 Aug 2026 03:37:32 +0900 Subject: [PATCH] =?UTF-8?q?decide(base):=20Modifier.Override=EB=A5=BC=20Ov?= =?UTF-8?q?erridden=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20=ED=99=95?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add/Remove→Added/Removed, Merge→Merged와 같은 분사형 네이밍 컨벤션을 Override에도 적용하되, override가 불규칙동사임을 반영해 정확한 과거분사 Overridden을 채택(Overrided는 오기). question.md 용어 재검토 목록에서 제거하고 관련 base/research 문서 전반에 반영. --- .claude/README.md | 2 +- .claude/base/architecture.md | 4 +- .claude/base/bind-system-plan.md | 2 +- .claude/base/component-composition-plan.md | 11 ++-- .claude/base/modifier-plan.md | 66 ++++++++++--------- .claude/base/tag-plan.md | 2 +- .claude/question.md | 16 +++-- .claude/research/documentation-content-map.md | 6 +- .claude/research/pre-implementation-audit.md | 22 +++---- CLAUDE.md | 29 ++++++++ ROADMAP.md | 9 +-- 11 files changed, 102 insertions(+), 67 deletions(-) diff --git a/.claude/README.md b/.claude/README.md index 76632e2..ff5a135 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -31,7 +31,7 @@ | `bind-system-plan.md` | pluggable key/value 핸들러 레지스트리 — `process`/`retract` 디스패치 모델, Ref, Store/State/Source 온톨로지 + 인체공학 질문 전부 확정. 디스패치 엔진은 `quad-base`가 인터페이스로 소유(2026-08-04 5차 라운드) | | `module-lifecycle-plan.md` | 프로바이더 패턴, bind/store 구현 책임 분리 — 확정 | | `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정 | -| `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정. **[2026-08-07 다섯 번째 세션 추가]** `:Apply(factory)` 팩토리 체이닝, `Override`(구 `Merge`) 값 결합+성능 기준, `:Peek`/`isState` 필드 읽기까지 전부 확정, 이름만 용어 정리 라운드까지 잠정 | +| `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정. **[2026-08-07 다섯 번째 세션 추가]** `:Apply(factory)` 팩토리 체이닝, `Overridden`(구 `Merge`→`Override`, 2026-08-08 세션에서 이름까지 확정) 값 결합+성능 기준, `:Peek`/`isState` 필드 읽기까지 전부 확정(`Peek`/`isState`는 이름만 용어 정리 라운드까지 잠정) | | `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 | | `component-composition-plan.md` | 컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, Source가 State를 구조적으로 만족 — modifier/Ref 컴포넌트 경계 통과까지 전부 확정, 남은 건 API 이름뿐. **[2026-08-07 정리]** 폐기된 `StoreSource` 프록시 설계로의 역전 이력은 본문에서 빼고 `archive/store-source-proxy-reversed.md` 포인터로 압축 | | `blocker-plan.md` | **[2026-08-07 신설]** `Blocker` — 여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게, State 마일스톤(M3)과 함께 개발. 메커니즘+이름 확정 | diff --git a/.claude/base/architecture.md b/.claude/base/architecture.md index 05ff75f..0cdca55 100644 --- a/.claude/base/architecture.md +++ b/.claude/base/architecture.md @@ -136,7 +136,7 @@ quad/ │ ├── State.luau # 캐시만 하는 non-owning 핸들, state(state) 분기, `:With`/`:Compute`/`:Observer`(등록 즉시 1회 실행) 전부 여기 소속 │ ├── Store.luau # source 집합체, dot-access로 Source 그대로 반환 │ ├── Blocker.luau # 값 기반 emit 지연/합치기(`base/blocker-plan.md`), State/Source와 밀접 연관돼 같은 위치 -│ ├── Modifier.luau # flatten-before-dispatch, immutable 체이닝, 제네릭 `__index` 필드 setter 합성 + `:Apply`/`:Peek`/`Override`(`base/modifier-plan.md`) +│ ├── Modifier.luau # flatten-before-dispatch, immutable 체이닝, 제네릭 `__index` 필드 setter 합성 + `:Apply`/`:Peek`/`Overridden`(`base/modifier-plan.md`) │ ├── Tag.luau # 값 타입+immutable clone 체이닝(`Tag(...)`/`:Added`/`:Removed`/`:Contains`/`:Apply`/`Merged`), CollectionService 글루는 quad-roblox Handlers/Tag.luau(`base/tag-plan.md`, 2026-08-08 세 번째 세션) │ ├── Effect.luau # `Effect(fn, state?)` — state 없으면 설치1회+leaf사망시 정리, 있으면 State.Observer를 조합해 재실행(`base/effect-plan.md`) │ ├── Dispatch/ @@ -190,7 +190,7 @@ Tween/existing-instance-bind는 여전히 `research/`에 남아있고 이 구조 `:Unsubscribe()`, `relate:SetWeak(...)`/`:GetWeak(...)`/`:SetStrong(...)`/ `:GetStrong(...)`, `mod:FontSize(...)`(필드 setter 체이닝). 3. 프리미티브 타입 자신의 네임스페이스에 달린 정적 결합 함수 — - `Modifier.Override(mod1, mod2, ...)`가 유일한 현재 사례. 콜론 메서드는 + `Modifier.Overridden(mod1, mod2, ...)`가 유일한 현재 사례. 콜론 메서드는 아니지만(여러 Modifier를 동등한 인자로 받아야 해서 self 하나로 안 됨) `Modifier` 타입 고유의 공개 연산이라는 점에서 1/2과 같은 부류 — `Modifier()` 생성자와 같은 이유로 대문자. diff --git a/.claude/base/bind-system-plan.md b/.claude/base/bind-system-plan.md index d36ec0c..453ad95 100644 --- a/.claude/base/bind-system-plan.md +++ b/.claude/base/bind-system-plan.md @@ -786,7 +786,7 @@ flatten된 값은 해시 파트(프로퍼티 키)로 존재하게 되고, Store (2026-08-07 아홉 번째 세션, 사용자 제안 채택) — 아직 문서화 안 돼 있었음, 지금 확정.** 위 "Modifier 필드로 막는 이유"/"Source/Store 값으로 막는 이유" 절은 **타입 차단**만 다뤘음 — Luau 타입은 런타임에 - 지워지므로(`:Peek`/`Override`/버그로 타입을 우회해 PreRef가 Modifier나 + 지워지므로(`:Peek`/`Overridden`/버그로 타입을 우회해 PreRef가 Modifier나 Store 값으로 실제로 흘러들어오는 경우), 런타임에도 방어가 필요함. 전용 `Handler`를 하나 등록: `{ isHandlable = function(inst,k,v) return isPreRef(v) end, process = function(inst,k,v) error("PreRef는 children diff --git a/.claude/base/component-composition-plan.md b/.claude/base/component-composition-plan.md index c504439..3d1f094 100644 --- a/.claude/base/component-composition-plan.md +++ b/.claude/base/component-composition-plan.md @@ -263,15 +263,15 @@ Fusion/Vide(named prop 전달, `[Children]`류 예약 키)가 서로 다른 이 가야 하는지는 저작자가 자기 코드에 뭐라고 쓰느냐로 완전히 결정됨(자동 전파가 없기 때문에 성립하는 단순함). -### 3. 여러 modifier를 하나로 합치는 공개 유틸 필요 — `Modifier.Override`(가칭, 2026-08-07 다섯 번째 세션에서 `Merge`→개명, 동작 확정) +### 3. 여러 modifier를 하나로 합치는 공개 유틸 필요 — `Modifier.Overridden`(2026-08-07 다섯 번째 세션에서 `Merge`→`Override`로 개명, 동작 확정; 2026-08-08 세션에서 `Overridden`으로 이름 확정) caller가 named parameter 하나에 여러 modifier를 몰아넣고 싶을 때 (`Frame{modifier1, modifier2}`의 컴포넌트판)를 위해, 기존 flatten 규칙(배열 순서상 나중 것이 필드 단위로 이김, `modifier-plan.md` 2번)을 그대로 재사용하는 -결합 함수를 공개 API로 노출: `Modifier.Override(mod1, mod2, ...) -> Modifier`. +결합 함수를 공개 API로 노출: `Modifier.Overridden(mod1, mod2, ...) -> Modifier`. 새 병합 규칙이 아니라 이미 확정된 flatten을 함수로 한 번 더 꺼내 쓸 수 있게 하는 것뿐 — **사용자 요청**("modifier를 합칠 방법도 존재한다면 좋을것 -같아"). `MyComp { Modifier = Modifier.Override(theme, override) }` → 컴포넌트 +같아"). `MyComp { Modifier = Modifier.Overridden(theme, override) }` → 컴포넌트 내부는 항상 이미 합쳐진 단일 값만 받으므로 컴포넌트 저작자가 배열 처리를 신경 쓸 필요 없음. Ref는 필드 충돌 개념이 없어 이 문제 자체가 없음(여러 Ref를 받으면 그냥 전부 실행하면 됨, `modifier-plan.md` §4-2) — 별도 결합 @@ -285,8 +285,9 @@ Ref를 받으면 그냥 전부 실행하면 됨, `modifier-plan.md` §4-2) — - **정확한 API 이름**: `Component`(플레인 함수 규약이라 별도 래퍼가 필요한지 자체도 불확실 — 아마 불필요), `Source`/`State` 독립 생성자·타입 이름, - 컴포넌트 경계용 `props.Modifier`/`props.Ref` 필드명, `Modifier.Override` - 함수명은 전부 가칭. (`GetSource` 계열 접근자는 위 3번 정정으로 아예 + 컴포넌트 경계용 `props.Modifier`/`props.Ref` 필드명은 전부 가칭 + (`Modifier.Overridden`은 2026-08-08 세션에서 이름 확정, 이 목록에서 + 빠짐). (`GetSource` 계열 접근자는 위 3번 정정으로 아예 불필요해짐 — `store.key`가 직접 Source를 반환하므로 별도 접근자 자체가 없음.) `base/bind-system-plan.md`의 "남은 열린 질문" 절(정확한 함수/ 생성자 이름 미정)과 같은 급의 후순위 항목 — 구현 단계에서 다른 diff --git a/.claude/base/modifier-plan.md b/.claude/base/modifier-plan.md index 8bc8ed8..756fa30 100644 --- a/.claude/base/modifier-plan.md +++ b/.claude/base/modifier-plan.md @@ -4,9 +4,9 @@ 값+clone 기반 체이닝, 이중 setter)은 2026-08-04 세션 채팅 논의로 확정. **Getter는 별도로 안 만들기로 확정(2026-08-06 후속 세션)** — 아래 "4. Setter는 리터럴 값과 변환 함수 둘 다 받음" 절 참고. **팩토리 함수 체이닝 -(`:Apply`), 값 결합(`Override`, 구 `Merge`), 필드 읽기(`:Peek`)+판별 +(`:Apply`), 값 결합(`Overridden`, 구 `Merge`), 필드 읽기(`:Peek`)+판별 (`isState`)은 2026-08-07 세션들에 걸쳐 확정 — 8/9번 절 참고, 한 줄 요약은 -`Apply`="변경을 수행", `Override`="이미 계산된 다른 mod를 합침".** Modifier가 +`Apply`="변경을 수행", `Overridden`="이미 계산된 다른 mod를 합침".** Modifier가 컴포넌트 경계를 어떻게 통과하는지(named parameter로 전달, multi-root 개념 폐기)는 별개 문제로 **[정정] `research/component-composition-plan.md`는 2026-08-04 세션에 수렴 완료돼 `base/component-composition-plan.md`로 @@ -69,11 +69,11 @@ Lua 테이블 리터럴은 배열 파트/해시 파트 사이에 소스 텍스 **디스패치 단계**에서 풀린다: - **`{ TextColor3 = None, mod }`도, `mod:TextColor3(None)`도 둘 다 지원.** - Modifier setter/Override/인라인 props 테이블은 `None`을 그냥 평범한 raw + Modifier setter/Overridden/인라인 props 테이블은 `None`을 그냥 평범한 raw 값으로 저장·교체할 뿐 특별 취급이 전혀 없음 — 애초에 문제였던 건 "`nil`이 테이블에 존재하는 값으로 표현이 안 된다"는 것뿐이라, 표현 가능한 실재 센티널만 있으면 기존 merge 규칙("인라인 키 존재 시 무조건 우선", - `Override`의 "뒤 인자가 필드 단위로 이김")이 손댈 것 없이 그대로 작동함. + `Overridden`의 "뒤 인자가 필드 단위로 이김")이 손댈 것 없이 그대로 작동함. 구현 비용이 사실상 0이라 인라인 키/setter 둘 다 여는 데 주저할 이유가 없음(2026-08-07 여덟 번째 세션 확정) — setter로 받으면 "특정 필드만 지우는 재사용 가능한 modifier 조각"(9-1번의 스타일 프리셋 opt-out 시나리오)도 @@ -294,7 +294,7 @@ setter 클로저(4번)와 이름이 겹치면 안 됨 — `__index`가 고정 **권장 관용구, 문서화 필요(2026-08-07 다섯 번째 세션)**: 특정 modifier를 계속 변형/보정하고 싶은 경우(스타일 프리셋, 커링된 팩토리 등)엔 항상 -`Apply`를 기본 선택지로 유도할 것 — 아래 9번의 `Override`는 "이미 따로 +`Apply`를 기본 선택지로 유도할 것 — 아래 9번의 `Overridden`는 "이미 따로 만들어진 modifier 값 두 개 이상을 합쳐야 하는" 경우로만 좁혀서 문서화(용도 구분 절 참고). @@ -306,7 +306,7 @@ setter 클로저(4번)와 이름이 겹치면 안 됨 — `__index`가 고정 저작자 책임. 문서에는 "`:Apply(f)`는 `f(mod)`를 체이닝 문법으로 쓴 것뿐, Apply 자체가 뭔가를 검증하거나 보장해준다고 오해하지 말 것"을 명시. -### 9. Modifier 결합 — `Modifier.Override(mod1, mod2, ...)`, `:Peek`, `isState` (2026-08-07 다섯 번째 세션) +### 9. Modifier 결합 — `Modifier.Overridden(mod1, mod2, ...)`, `:Peek`, `isState` (2026-08-07 다섯 번째 세션) **배경**: `base/component-composition-plan.md` 3번 절이 이미 "여러 modifier를 하나로 합치는 공개 유틸이 필요하다"고 확정하며 `Modifier.Merge` @@ -318,46 +318,46 @@ modifier를 하나로 합치는 공개 유틸이 필요하다"고 확정하며 ` `Apply`로 완전 대체/강제 통합하는 방안도 이번에 검토했으나 이 실사용 니즈를 못 풀어서 기각). 이번 세션에서 실제 동작을 확정. -**이름 변경**: `Merge` → **`Override`로 확정**(사용자 제안). "Merge"는 +**이름 변경**: `Merge` → **`Overridden`로 확정**(사용자 제안). "Merge"는 중립적 합침을 암시하지만 실제 동작은 명시적으로 나중 인자가 이기는 "덮어쓰기"라, 이름이 의미를 정직하게 반영해야 함 — `component-composition-plan.md`의 참조도 이번에 같이 갱신함. **용도를 좁게 문서화할 것 — "진짜 합칠 필요가 있는 경우"로 한정 -(2026-08-07 다섯 번째 세션, 사용자 강조).** `Override`는 범용 조합 +(2026-08-07 다섯 번째 세션, 사용자 강조).** `Overridden`는 범용 조합 도구가 아니라 `Frame{mod1, mod2}`의 컴포넌트 경계판, 그 이상도 이하도 아님 — 이게 없으면 `props.Modifier` 같은 단일 슬롯에 여러 독립 modifier 값을 넣을 방법이 아예 없어지므로 프리미티브로 남겨두는 것뿐. **"특정 -modifier를 계속 바꿔나가고 싶다"는 요구는 `Override`가 아니라 위 8번 +modifier를 계속 바꿔나가고 싶다"는 요구는 `Overridden`가 아니라 위 8번 `Apply`로 풀도록 유도** — 간결한 커링/일급 함수 전달이 기본 관용구가 -되도록, API 문서에서 `Override`를 "값 두 개 이상을 합쳐야 하는 특수 +되도록, API 문서에서 `Overridden`를 "값 두 개 이상을 합쳐야 하는 특수 상황"으로만 소개하고 스타일 변형/보정의 기본 진입점으로는 절대 먼저 보여주지 않을 것. **동작 = 기존 flatten을 함수로 노출한 것, 새 규칙 없음.** -`Modifier.Override(mod1, mod2, ...)`는 뒤 인자가 필드 단위로 이긴다(2번 +`Modifier.Overridden(mod1, mod2, ...)`는 뒤 인자가 필드 단위로 이긴다(2번 절 "배열 순서" 규칙과 동일). 구현은 단순 필드별 덮어쓰기 — 특별한 State/함수 분기가 필요 없음: setter가 이미 호출 시점에 함수를 즉시 실행하고 State 필드는 즉시 `:Compute`로 파생시켜 저장하므로(4번/4-1번), Modifier가 들고 있는 모든 필드는 항상 "그 시점에 이미 완전히 처리된 -(baked) 값"임 — `Override`는 그 baked 값을 필드별로 그대로 교체할 뿐. +(baked) 값"임 — `Overridden`는 그 baked 값을 필드별로 그대로 교체할 뿐. **경고, 반드시 문서화**: baked 값 교체는 그 필드에서 파생된 다른 필드에 소급 반영되지 않는다. 예: `Boldify`가 `Font` 필드를 읽어(`Peek`, 아래 참고) `FontWeight`를 계산해 넣어둔 modifier를, 나중에 `Font`를 바꾸는 -다른 modifier와 `Override`로 합치면 `Font`는 새 값으로 바뀌지만 +다른 modifier와 `Overridden`로 합치면 `Font`는 새 값으로 바뀌지만 `FontWeight`는 예전 `Font` 기준으로 계산된 채 그대로 남는다 — 사용자 실수 범주지만 조용히 틀린 결과가 나오는 케이스라 API 문서(경고 박스)로 -명시 필요. `A:Override(B)`와 `B:Override(A)`가 다른 결과를 낸다는 순서 +명시 필요. `A:Overridden(B)`와 `B:Overridden(A)`가 다른 결과를 낸다는 순서 의존성도 같은 경고 박스에 같이 명시. -**용도 구분 — `Apply` vs `Override`, 둘 다 유지, 서로 대체 안 함**: -한 줄로 요약하면 **`Apply`는 "특정 대상에 대해 변경을 수행한다", `Override`는 +**용도 구분 — `Apply` vs `Overridden`, 둘 다 유지, 서로 대체 안 함**: +한 줄로 요약하면 **`Apply`는 "특정 대상에 대해 변경을 수행한다", `Overridden`는 "특정 대상에 이미 계산된(baked) 다른 mod를 합친다"** — 문서화 시 이 한 문장을 그대로 핵심 구분 기준으로 앞세울 것(2026-08-07 다섯 번째 세션, 사용자 정리). 재사용 가능한 스타일 "변형"(팩토리, 파라미터화 가능)은 `Apply`, 독립적으로 이미 만들어진 modifier "값" 두 개 이상을 한 슬롯에 -밀어넣어야 하는 경우(주로 컴포넌트 경계)는 `Override`. +밀어넣어야 하는 경우(주로 컴포넌트 경계)는 `Overridden`. **9-1. 판단 기준을 "이질적/동질적"이 아니라 "계산 의존성 유무"로 명시할 것, `Apply`를 mutable로 바꾸는 방안은 기각 (2026-08-07 다섯 번째 세션 후속)** @@ -365,7 +365,7 @@ Modifier가 들고 있는 모든 필드는 항상 "그 시점에 이미 완전 **동기**: `Apply` 체이닝이 호출마다 clone을 만들기 때문에, 항목 수천 개짜리 리스트 UI처럼 무거운 Modifier를 대량으로 재생성하는 상황에서 이 clone 비용이 누적되는 게 아닌지 사용자가 우려 — 대안으로 (a) `Apply`/setter를 -아예 mutable로 바꾸는 방안, (b) `Override`를 "여러 값을 합칠 특수 상황"이 +아예 mutable로 바꾸는 방안, (b) `Overridden`를 "여러 값을 합칠 특수 상황"이 아니라 "성능 최적화 수단"으로 승격하는 방안을 검토. **(a) `Apply`를 mutable로 바꾸는 방안 — 기각.** 3번 절에서 immutable+clone을 @@ -396,13 +396,13 @@ setter들은 그 복사본을 그대로 mutate). **기각 이유**: 이렇게 **(b) 판단 기준 — "동질적 vs 이질적 프로퍼티"가 아니라 "필드 간 계산 의존성 유무"로 명시.** 사용자가 처음엔 "동질적(폰트 굵기 보정처럼 연관된 속성끼리)은 `Apply`, 이질적(배경/텍스트/위치처럼 무관한 속성끼리)은 -`Override`"로 구분을 제안했으나, 실제 기준은 주제의 이질성 자체가 아니라 +`Overridden`"로 구분을 제안했으나, 실제 기준은 주제의 이질성 자체가 아니라 **한쪽이 다른 쪽의 이미 baked된 값을 읽어야 하는가(`Peek`으로 데이터가 흘러가는가)**임 — 이질적으로 보여도 계산 의존성이 있으면 `Apply`가 맞고(예: "배경색에 맞춰 텍스트 명도를 자동 보정" — 배경/텍스트라는 이질적 주제인데도 의존성이 있어 `Peek`+`Apply`가 필요), 반대로 동질적으로 보여도 서로 완전히 독립이면(예: 여러 개의 `FontSize` 프리셋 중 하나를 통째로 -갈아끼우는 경우) `Override`도 무방함. `Override`는 필드 단위 raw 교체일 +갈아끼우는 경우) `Overridden`도 무방함. `Overridden`는 필드 단위 raw 교체일 뿐 `Peek`으로 값을 읽어 다른 필드에 반영하는 데이터 흐름이 아예 없으므로 (위 "동작" 절), 계산 의존성이 있는 조합엔 애초에 못 씀 — 이게 진짜 판별 기준. 문서에는 "이질적/동질적"이라는 표면적 구분 대신 이 기준으로 적을 것. @@ -410,19 +410,19 @@ setter들은 그 복사본을 그대로 mutate). **기각 이유**: 이렇게 **실제 최적화 권장 패턴**: 계산 의존성이 없고 재사용 가능한 조각(예: 배경 스타일 하나, 텍스트 스타일 하나, 레이아웃 위치 하나 — 각각 서로 다른 서브시스템/모듈에서 한 번만 만들어지는 값)은 **모듈 상수/한 번만 -생성한 값으로 만들어두고, 인스턴스마다 `Override`로 결합**하는 게 +생성한 값으로 만들어두고, 인스턴스마다 `Overridden`로 결합**하는 게 `Apply` 체인으로 매번 처음부터 다시 파생시키는 것보다 저렴함 — 조각 자체를 -매번 재계산 안 해도 되고, `Override`는 필드별 단순 복사 한 번으로 끝나서 -여러 단계 clone이 누적되는 `Apply` 체인보다 쌈. **주의**: 이건 "`Override`가 -내부적으로 값을 캐싱해준다"는 뜻이 아님 — `Override` 자체엔 캐싱/메모이제이션 +매번 재계산 안 해도 되고, `Overridden`는 필드별 단순 복사 한 번으로 끝나서 +여러 단계 clone이 누적되는 `Apply` 체인보다 쌈. **주의**: 이건 "`Overridden`가 +내부적으로 값을 캐싱해준다"는 뜻이 아님 — `Overridden` 자체엔 캐싱/메모이제이션 같은 새 메커니즘이 전혀 없고(순수 필드 복사), "캐싱"은 그냥 사용자가 조각 Modifier 값을 변수/모듈 상수로 만들어 재사용하는, 기존에도 항상 가능했던 평범한 값 재사용일 뿐 — 라이브러리에 새 캐싱 레이어를 추가하는 게 아니라는 점을 문서에서 분명히 할 것(라이브러리 차원의 자동 메모이제이션은 지금 검토 대상 아님 — 실제로 필요하다고 확인되면 그때 별도로 논의). -**문서 배치**: 초심자 문서엔 `Override`를 아예 안 보여주고(위 "용도를 좁게 -문서화" 절), 이 "언제 `Apply` vs `Override`, 성능 기준" 절 전체는 api/심화 +**문서 배치**: 초심자 문서엔 `Overridden`를 아예 안 보여주고(위 "용도를 좁게 +문서화" 절), 이 "언제 `Apply` vs `Overridden`, 성능 기준" 절 전체는 api/심화 문서 전용 — `research/documentation-content-map.md`의 modifier-plan.md 분류에 반영 완료. @@ -431,14 +431,14 @@ Modifier 값을 변수/모듈 상수로 만들어 재사용하는, 기존에도 근거 없는 선제 최적화라 설계하지 않음 — CLAUDE.md의 "드문 오용/가상 미래 요구까지 방어/최적화하려고 구조를 복잡하게 만들지 않는다" 원칙과 동일. -### 9-2. `Override`가 서로 다른(그러나 상하위 관계인) Modifier 타입을 섞는 경우 — +### 9-2. `Overridden`가 서로 다른(그러나 상하위 관계인) Modifier 타입을 섞는 경우 — 타입 시그니처 미확정, 실 Luau 테스트 필요 (2026-08-07 다섯 번째 세션 후속) **문제**: Modifier 타입은 위 4번 절 "FrameModifier 타입" 언급대로 Roblox 클래스별로 생성기가 뽑아내는 flat 타입인데, 그 밑의 Roblox 클래스 자체엔 서브타입 관계가 있음(`Frame`이 `GuiObject`의 서브클래스) — 그럼 생성된 `FrameModifier`도 `GuiObjectModifier`의 서브타입이어야 자연스럽고, 실제로 -`Modifier.Override(guiObjectMod, frameMod)`처럼 공통 상위 클래스 스타일 +`Modifier.Overridden(guiObjectMod, frameMod)`처럼 공통 상위 클래스 스타일 프리셋과 하위 클래스 전용 보정을 섞어 합치는 패턴이 필요해 보임(사용자 지적, 2026-08-07). @@ -456,7 +456,7 @@ Modifier 값을 변수/모듈 상수로 만들어 재사용하는, 기존에도 "추론만으로 확정하고 실제 Luau로 부딪혀본 적 없는 것"과 같은 성격의 모호함). **당장의 fallback**: 위 후보안이 Luau에서 실제로 안 먹히는 걸로 확인되면, -`Modifier.Override`의 타입 시그니처를 일단 `Override(...: any): any`류로 +`Modifier.Overridden`의 타입 시그니처를 일단 `Overridden(...: any): any`류로 느슨하게 열어 정적 체크를 포기 — 이건 임시 처치로 명시하고, M7 실제 구현 시점에 실 테스트 결과에 따라 다시 좁히는 걸 목표로 로드맵에 남김 (`ROADMAP.md` M7). @@ -505,6 +505,8 @@ Source도 같이 잡아줌 — **[2026-08-07 여덟 번째 세션 정정] `isSou `base/component-composition-plan.md`에서 해소됨**(named parameter로 전달, "다중 루트" 개념 자체는 폐기) — 더 이상 열린 질문 아님, 이 문서가 다루는 "값 자체의 동작"과는 별개 문제였다는 점만 참고로 남김. -- `Override`/`Peek`/`isState` — 동작은 위 9번 절에서 확정, 정확한 이름은 - 다른 가칭들과 마찬가지로 `.claude/question.md`의 용어 정리 라운드까지 - 잠정. +- **[해소됨]** `Overridden` 이름 — 2026-08-08 세션에서 확정(`Add`/`Remove` + →`Added`/`Removed`, `Merge`→`Merged`와 같은 분사형 네이밍 컨벤션에 + 맞춰 불규칙동사 `override`의 정확한 과거분사를 씀, `Overrided`는 오기). + `Peek`/`isState` — 동작은 위 9번 절에서 확정, 정확한 이름은 다른 + 가칭들과 마찬가지로 `.claude/question.md`의 용어 정리 라운드까지 잠정. diff --git a/.claude/base/tag-plan.md b/.claude/base/tag-plan.md index 345777f..02478ea 100644 --- a/.claude/base/tag-plan.md +++ b/.claude/base/tag-plan.md @@ -21,7 +21,7 @@ tag:Removed(name): Tag -- clone 후 이름 제거 tag:Contains(name): boolean -- 멤버십 확인 tag:Apply(factory): U -- factory(self) 체이닝 설탕(Modifier와 동일 패턴) Tag.Merged(tag1, tag2, ...): Tag -- 여러 Tag의 합집합(무손실). Modifier의 - Override(필드 단위 덮어쓰기, 손실 있음)와 + Overridden(필드 단위 덮어쓰기, 손실 있음)와 다른 연산이라 이름도 다름 — Override는 "이미 계산된 걸 합침", Merged는 "집합을 합침" diff --git a/.claude/question.md b/.claude/question.md index 03a2824..2f1d66d 100644 --- a/.claude/question.md +++ b/.claude/question.md @@ -84,11 +84,13 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만** 즉시모드 GUI 뉘앙스), `List`(중립적이나 메커니즘을 안 알려줌) — 아직 미정, `research/additional-primitives-plan.md`의 "키 기반 동적 컬렉션 재조정" 절 참고. -- **`Override`/`Peek`/`isState`(3순위, 사소함, 2026-08-07 다섯 번째 - 세션 추가)**: Modifier 결합 유틸(구 `Merge`)과 필드 읽기 접근자, State/ - Source 판별 predicate 세 개의 이름 — 동작은 전부 확정(`base/ - modifier-plan.md` 9번, `base/bind-system-plan.md`의 `isState` 절), - 이름만 다른 가칭들과 같이 용어 정리 라운드에서 재검토. +- **`Peek`/`isState`(3순위, 사소함, 2026-08-07 다섯 번째 세션 추가)**: + 필드 읽기 접근자, State/Source 판별 predicate 두 개의 이름 — 동작은 + 전부 확정(`base/modifier-plan.md` 9번, `base/bind-system-plan.md`의 + `isState` 절), 이름만 다른 가칭들과 같이 용어 정리 라운드에서 재검토. + (`Override`는 이 목록에서 빠짐 — `Overridden`으로 확정, 2026-08-08 + 세션. `Add`/`Remove`→`Added`/`Removed`, `Merge`→`Merged`와 같은 분사형 + 네이밍 컨벤션에 맞춰 불규칙동사 `override`의 정확한 과거분사를 씀.) - **`Bound`(3순위, 사소함, 2026-08-07 일곱 번째 세션 추가)**: Observer/ Effect 핸들이 leaf 부착과 `:Subscribe()` 중 이미 어느 한쪽으로 바인딩됐는지 표시하는 내부 플래그 이름(`base/bind-system-plan.md` @@ -231,8 +233,8 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만** | 프로바이더 패턴, bind/store 구현 책임 분리 | `base/module-lifecycle-plan.md` | | Slot 재조정, 재마운트 시 throw, retract=폐기 | `base/slot-plan.md` | | `Connected`+GC 라이프사이클 패턴 | `base/lifecycle-pattern.md` | -| Modifier(정적 merge, immutable 체이닝, State 필드 지원, `Apply`/`Override`/`Peek`/`isState`) | `base/modifier-plan.md` | -| 컴포넌트화(플레인 함수, State/Source 경계, 컴포넌트 경계 modifier/Ref는 named parameter로 전달, multi-root 개념 폐기, `Modifier.Override`) | `base/component-composition-plan.md` | +| Modifier(정적 merge, immutable 체이닝, State 필드 지원, `Apply`/`Overridden`/`Peek`/`isState`) | `base/modifier-plan.md` | +| 컴포넌트화(플레인 함수, State/Source 경계, 컴포넌트 경계 modifier/Ref는 named parameter로 전달, multi-root 개념 폐기, `Modifier.Overridden`) | `base/component-composition-plan.md` | | 컴포넌트 이식성(전역 store 참조 시 재사용성 문제) | `base/purity-and-effects-plan.md` | | Blocker(값 기반 emit 지연/합치기) | `base/blocker-plan.md` | | Effect(설치+확정 정리, `state` 있으면 Observer 조합해 재실행도 지원 — 확정) | `base/effect-plan.md` | diff --git a/.claude/research/documentation-content-map.md b/.claude/research/documentation-content-map.md index 55e5639..7379632 100644 --- a/.claude/research/documentation-content-map.md +++ b/.claude/research/documentation-content-map.md @@ -74,7 +74,7 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부 ### component-composition-plan.md / module-lifecycle-plan.md - 초심자: 컴포넌트=순수 함수 / 리프 프로퍼티엔 State만 바인딩 / `props.Modifier`/`props.Ref` named parameter 경계 전달(**`props.Modifier or None`/`props.Ref or None` 필수 관용구 — 안 쓰면 nil-hole 버그, 2026-08-07 열 번째 세션 확정**) / `InitRoblox(Module)` 팩토리 초기화 -- api: State(파생, 읽기전용) vs Source(원본, 쓰기가능) 경계 요약(→심화) / Slot 반환 컴포넌트는 Modifier/Ref 파라미터 미선언 / `Modifier.Override(mod1, mod2, ...)` 유틸(구 `Merge`, `props.Modifier` 단일 슬롯용 특수 상황으로 한정 소개 — 아래 modifier-plan.md 절 참고) / Bind는 유일 슬롯(재호출 no-op, 충돌 에러, →심화) / `:With`/`:Compute`로 파생 State 생성 시그니처 / 모듈 싱글톤 스코프 +- api: State(파생, 읽기전용) vs Source(원본, 쓰기가능) 경계 요약(→심화) / Slot 반환 컴포넌트는 Modifier/Ref 파라미터 미선언 / `Modifier.Overridden(mod1, mod2, ...)` 유틸(구 `Merge`, `props.Modifier` 단일 슬롯용 특수 상황으로 한정 소개 — 아래 modifier-plan.md 절 참고) / Bind는 유일 슬롯(재호출 no-op, 충돌 에러, →심화) / `:With`/`:Compute`로 파생 State 생성 시그니처 / 모듈 싱글톤 스코프 - 심화: v1 `Extend` 자동 store 소유 폐지 이유(React 벤치마킹) / Source가 State를 구조적으로 만족하는 서브타입 설계(2026-08-06 후속 세션 — `StoreSource` 프록시 중간안은 폐기되고 이걸로 대체됨, `store-semantics.md` 참고) / named-parameter 경계 방식 채택 이유(Compose/Fusion/Vide/v1 선례 수렴) / 다중 루트 반환 개념 제거 근거 / 팩토리 초기화 패턴 채택 이유(RBVM `InitNamespace` 반례) / Store 책임 분리(base가 `LifetimeHandle` 소유) / v1 named 체이닝 연산 폐기 - skip: Compose/Fusion/Vide/v1 프레임워크 비교 원자료 / provider/processor 네이밍 미정 등 열린 질문 메모 @@ -86,8 +86,8 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부 ### modifier-plan.md / slot-plan.md - 초심자: Modifier 기본 체이닝+merge 우선순위 규칙 실제 예시 / Slot 기본 개념(children 배열)+클래스가 슬롯 받는 방법(Named Slot 없음) / 마운트된 slot 재마운트 시 즉시 throw -- api: Setter가 리터럴/변환 함수 둘 다 받음(→심화: getter 없는 이유) / 필드가 State일 수 있는 4가지 조합 표(→심화: 반응성 유지/끊김 이유) / `mod:UICorner(8)` dot-access 생성자 관습 / Slot은 인스턴스당 여럿 가능 / 중첩 인스턴스 자식 처리 / retract 시 slot 내용 폐기(→심화: portal 없는 이유) / `:Apply(factory)` 기본 체이닝 관용구(→심화: 언제 `Apply` vs `Override`인지 성능 기준) / `:Peek<>(key)` + `isState`(→심화: `Get`과 이름을 다르게 한 이유) -- 심화: 정적 merge vs 런타임 pluggable 기각 이유(CSS cascade) / immutable+clone 체이닝 이유(형제 오염 방지) / getter 미채택 이유 / `__index` 런타임 구현 통찰 / Modifier가 핸들러 계층을 모르는 이유 / base/roblox 패키지 경계(Dispatch/Slot vs Handlers/Slot) / Slot 단일 마운트 소유권이 v1/Fusion/Vide 대비 개선인 이유 / retract=폐기 확정 히스토리(portal 검토 후 기각) / **왜 `Apply`가 기본이고 `Override`는 최적화 특수 케이스인가**(계산 의존성 있는 조합 vs 독립적 재사용 가능 조각의 병합 — 2026-08-07 다섯 번째 세션, `modifier-plan.md` 9번) / 왜 `Apply`가 clone 대신 mutate하지 않는가(형제 오염 방지가 개별 clone 비용 절감보다 우선) +- api: Setter가 리터럴/변환 함수 둘 다 받음(→심화: getter 없는 이유) / 필드가 State일 수 있는 4가지 조합 표(→심화: 반응성 유지/끊김 이유) / `mod:UICorner(8)` dot-access 생성자 관습 / Slot은 인스턴스당 여럿 가능 / 중첩 인스턴스 자식 처리 / retract 시 slot 내용 폐기(→심화: portal 없는 이유) / `:Apply(factory)` 기본 체이닝 관용구(→심화: 언제 `Apply` vs `Overridden`인지 성능 기준) / `:Peek<>(key)` + `isState`(→심화: `Get`과 이름을 다르게 한 이유) +- 심화: 정적 merge vs 런타임 pluggable 기각 이유(CSS cascade) / immutable+clone 체이닝 이유(형제 오염 방지) / getter 미채택 이유 / `__index` 런타임 구현 통찰 / Modifier가 핸들러 계층을 모르는 이유 / base/roblox 패키지 경계(Dispatch/Slot vs Handlers/Slot) / Slot 단일 마운트 소유권이 v1/Fusion/Vide 대비 개선인 이유 / retract=폐기 확정 히스토리(portal 검토 후 기각) / **왜 `Apply`가 기본이고 `Overridden`는 최적화 특수 케이스인가**(계산 의존성 있는 조합 vs 독립적 재사용 가능 조각의 병합 — 2026-08-07 다섯 번째 세션, `modifier-plan.md` 9번) / 왜 `Apply`가 clone 대신 mutate하지 않는가(형제 오염 방지가 개별 clone 비용 절감보다 우선) - 열린 질문(문서화 보류): 여러 Slot이 형제로 섞일 때 순서 보장 — 미확정 - skip: 세션 날짜/확정 이력, 문서 승격/정정 안내 diff --git a/.claude/research/pre-implementation-audit.md b/.claude/research/pre-implementation-audit.md index 54f2b1e..b1e7633 100644 --- a/.claude/research/pre-implementation-audit.md +++ b/.claude/research/pre-implementation-audit.md @@ -370,29 +370,29 @@ API"를 얹어야 할 때 Slot의 "own한 것만 CRUD 대상" 불변식 자체 `existing-instance-bind-plan.md`에 한 줄 추가해둘 것 — 기존 "Modifier flatten과 긴장" 캐비엇 옆에 병기. -### 2-5. `Modifier.Override`(구 `Merge`) 시 서로 다른 클래스의 Modifier가 섞이는 게 허용되는지 — **런타임은 해소, 타입 레벨은 여전히 미정** +### 2-5. `Modifier.Overridden`(구 `Merge`) 시 서로 다른 클래스의 Modifier가 섞이는 게 허용되는지 — **런타임은 해소, 타입 레벨은 여전히 미정** -**위치**: `base/modifier-plan.md` "2. Merge 우선순위" + 9번(`Override`) + +**위치**: `base/modifier-plan.md` "2. Merge 우선순위" + 9번(`Overridden`) + `base/component-composition-plan.md` 3번. **[2026-08-07 다섯 번째 세션 갱신] 런타임 동작은 이제 명확함**: `modifier-plan.md` -9번에서 `Override`가 "필드별 raw 덮어쓰기"로 확정됐고, "Modifier는 핸들러 +9번에서 `Overridden`가 "필드별 raw 덮어쓰기"로 확정됐고, "Modifier는 핸들러 계층을 모름 — 순수 데이터 merge 레이어"(1번 절) 원칙도 이미 있었으므로, -**런타임 레벨에서는 대상 클래스가 다른 Modifier끼리 `Override`해도 막을 +**런타임 레벨에서는 대상 클래스가 다른 Modifier끼리 `Overridden`해도 막을 이유가 없음**(필드명만 보고 그대로 덮어쓸 뿐 — Luau 타입은 런타임에 강제되지 않는다는 점은 `store-semantics.md`에도 이미 명시된 전제). **여전히 미정인 것 — 타입 레벨**: Modifier가 target 클래스별 제네릭 -타입(`Modifier` 등)이라면, `Modifier.Override(mod1: Modifier, +타입(`Modifier` 등)이라면, `Modifier.Overridden(mod1: Modifier, mod2: Modifier): Modifier`처럼 같은 `T`만 받도록 타입으로 강제할지, 아니면 공통 base 타입(여러 GuiObject 클래스에 걸친 공통 필드)과 클래스별 -확장 사이의 계층 구조를 별도로 두고 `Override`가 그 계층을 넘나들 수 +확장 사이의 계층 구조를 별도로 두고 `Overridden`가 그 계층을 넘나들 수 있게 할지는 아직 결정된 바 없음 — "공통 테마 Modifier + 클래스별 override -Modifier를 합친다"는 시나리오가 `Override`의 가장 그럴듯한 실사용 +Modifier를 합친다"는 시나리오가 `Overridden`의 가장 그럴듯한 실사용 예시라 이 타입 설계가 실제로 막히면 바로 걸릴 문제. **제안**: Modifier의 클래스별 typed 생성자 계층(2-8번과 같은 지점) 설계 -시 `Override`의 제네릭 시그니처도 같이 확정할 것. M7 착수 시. +시 `Overridden`의 제네릭 시그니처도 같이 확정할 것. M7 착수 시. ### 2-6. Modifier 필드에 State/Source를 인자로 넘기는 케이스가 세터 표에서 빠짐 @@ -412,10 +412,10 @@ Modifier를 합친다"는 시나리오가 `Override`의 가장 그럴듯한 실 ### 2-7. 여러 Ref를 하나의 named parameter로 넘길 때 nested-array flatten 여부 불명 -**위치**: `base/component-composition-plan.md` 3번(`Modifier.Override`, +**위치**: `base/component-composition-plan.md` 3번(`Modifier.Overridden`, 구 `Merge`) vs "Ref는... 별도 결합 유틸 불필요" 문장. -**문제**: Modifier는 여러 개를 합치려면 `Modifier.Override`가 명시적으로 +**문제**: Modifier는 여러 개를 합치려면 `Modifier.Overridden`가 명시적으로 필요한데, 바로 다음 문장은 Ref는 "여러 Ref를 받으면 그냥 전부 실행하면 됨 — 별도 결합 유틸 불필요"라고 한다. `props.Ref = {ref1, ref2}`처럼 배열을 넘기면 리프 디스패처가 그 중첩 배열을 재귀적으로 펼쳐서 각 Ref를 @@ -437,7 +437,7 @@ Modifier를 합친다"는 시나리오가 `Override`의 가장 그럴듯한 실 것)은 "DI 쪽 '제네릭 생성자 함수 하나 + 자주 쓰는 것만 정적 필드' 패턴 재사용"이라 문서 스스로 밝히듯 quad-roblox의 DI 타입 생성 계층(M5)에 강하게 결합돼 있다. 그런데 `ROADMAP.md` M7 체크리스트(flatten-before-dispatch, -`Modifier.Override`, `State` 차단)엔 이 클래스별 타입 생성 작업이 +`Modifier.Overridden`, `State` 차단)엔 이 클래스별 타입 생성 작업이 전혀 없고, M5 DI 체크리스트에도 Modifier 언급이 없음. **제안**: M5 또는 M7 체크리스트에 "quad-roblox 클래스별 typed Modifier diff --git a/CLAUDE.md b/CLAUDE.md index f62e722..d7e09bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1628,3 +1628,32 @@ realv 타입이 매 갱신마다 바뀔 수 있는데 '이전 핸들러'를 누 핸들러인지"가 불명확한 문제)은 Tag가 두 번째 구체 사례가 되면서 정황상 "별개 핸들러, 둘 다 `Dispatch/StoreBind.luau` 재사용"쪽에 힘이 실리지만 **아직 명시적으로 확정된 건 아님** — M2/M4 착수 전 마저 확인할 것. + +## 2026-08-08 네 번째 세션 — `Modifier.Override` → `Overridden`으로 이름 확정 + +사용자가 IDE에서 `tag-plan.md`를 보다가 "Tag가 `Added`/`Removed`처럼 +`-ed` 어미를 의도적으로 쓰는데, Modifier의 `Override`도 그냥 +`Overrided`로 하면 어떤가"라고 질문 — `-ed`/분사 어미가 "즉시 커밋되는 +뮤테이션이 아니라 이미 계산되어 반환되는 새 값"을 신호한다는 기존 관례 +(`Add`/`Remove`가 `-ed` 없이 쓰이면 뮤테이션처럼 오독될 위험이 있어 +`Added`/`Removed`로 확정했던 것과 같은 문제가 `Override`에도 그대로 +있음)에 정확히 들어맞는 좋은 관찰이었음. 다만 `Overrided`는 오기 — +`override`는 불규칙동사라 과거분사가 `overrided`가 아니라 `overridden`. +`Add`/`Remove`/`Merge`가 전부 규칙동사라 우연히 단순 `-ed` 접미만으로 +맞았던 것뿐, `Override`엔 그 규칙이 그대로 안 통함. 사용자가 이 정정에 +동의하고 확정 요청 — `Modifier.Overridden(mod1, mod2, ...)`으로 이름 +자체를 확정(더 이상 가칭 아님, 용어 정리 라운드 대상에서도 제외). + +`base/modifier-plan.md`/`base/component-composition-plan.md`/ +`base/bind-system-plan.md`/`base/tag-plan.md`(비교 문구)/ +`base/architecture.md`/`ROADMAP.md`/`research/pre-implementation-audit.md`/ +`research/documentation-content-map.md`/`.claude/README.md`/ +`.claude/question.md` 전부에서 `Override` → `Overridden`으로 기계적 +치환 + 각 문서의 "가칭"/"이름만 잠정" 표시를 "이름 확정"으로 갱신 +(`question.md`의 3순위 용어 재검토 목록에선 완전히 제거, `Peek`/ +`isState`만 그 목록에 남음). CLAUDE.md 세션 히스토리(과거 `Override` +서술)와 `archive/`는 당시 기록이라 그대로 둠 — 역사적 서술과 현재 +유효한 이름을 헷갈리지 않도록 여기 새 절로만 반영. + +**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터) — 순수 네이밍 +확정이라 M0 착수 우선순위나 설계 자체엔 영향 없음. diff --git a/ROADMAP.md b/ROADMAP.md index 456833e..3aca8d7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -180,11 +180,12 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 - [ ] flatten-before-dispatch(`isModifier(v)`로 배열 항목 중 Modifier만 판별해 필드 merge, 나머지는 안 건드리고 통과 — 2026-08-07 열 번째 세션 명시, `modifier-plan.md` 1번), immutable `table.clone` 체이닝 -- [ ] `Modifier.Override(mod1, mod2, ...)`(가칭, 구 `Merge`) — 필드별 raw - 덮어쓰기, 특별한 State/함수 분기 불필요(`modifier-plan.md` 9번) -- [ ] `Override`가 서브타입 관계인 서로 다른 Modifier 타입(예: `FrameModifier`/ +- [ ] `Modifier.Overridden(mod1, mod2, ...)`(이름 확정, 구 `Merge`→`Override`, + 2026-08-08 세션) — 필드별 raw 덮어쓰기, 특별한 State/함수 분기 + 불필요(`modifier-plan.md` 9번) +- [ ] `Overridden`가 서브타입 관계인 서로 다른 Modifier 타입(예: `FrameModifier`/ `GuiObjectModifier`)을 섞을 때의 타입 시그니처 실 Luau 테스트 - (`modifier-plan.md` 9-2번, 미검증 — 안 되면 일단 `Override(...: any): + (`modifier-plan.md` 9-2번, 미검증 — 안 되면 일단 `Overridden(...: any): any`로 느슨하게 열어두고 이 항목으로 되돌아올 것) - [ ] `State` 조합 타입 차단 확인(`modifier-plan.md` 7번, UB 확정) - [ ] `:Apply(factory)` 팩토리 함수 체이닝(`modifier-plan.md` 8번, 예약 키