merge: main의 최신 리서치/감사 반영 (문서사이트 구조, Source/State 서브타입 재구성, M0 크리티컬 감사)

# Conflicts:
#	.claude/README.md
This commit is contained in:
qwreey 2026-08-06 21:54:46 +09:00
commit 6c99de744d
Signed by: qwreey
GPG key ID: D28DB79297A214BD
13 changed files with 1476 additions and 91 deletions

View file

@ -12,7 +12,7 @@
| `base/` | 결정 완료 + 프로젝트 전체에 걸치는 컨텍스트 — plan/done 개념 없음, 계속 참조되는 배경지식 |
| `research/` | 아직 착수 전, 사용자와 스코프/설계를 더 상의해야 함 |
| `qa-request/` | 구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음 — 지금은 구현 자체가 시작 전이라 비어있음 |
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 — 지금은 비어있음 |
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). **[2026-08-06 확장]** 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용 — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 `base/`/`research/`에서 뺌) `quadnomicon` 소재로는 나중에 쓸 수 있음 |
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — 지금은 비어있음(구현 시작 전) |
| `initreq/` | 프로젝트 착수 시 클론해둔 참고 레포(quad v1, fusion, vide, rbvm, tbox, code-docker) + PA님 실 코드(`artworks/`, 4차 라운드 교차검증 근거) + 원본 요청(`req.md`, `raw-userinput.md`) + `quad2-try`(이전에 시도했다 폐기한 v2 재작성 시도 — 리서치 완료, 결론은 `base/bind-system-plan.md`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
@ -34,6 +34,7 @@
| `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정 |
| `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정, getter 이름만 남음 |
| `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 |
| `component-composition-plan.md` | 컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, Source가 State를 구조적으로 만족(`StoreSource`/`RefSource` 중간안은 전부 폐기됨) — modifier/Ref 컴포넌트 경계 통과까지 전부 확정, 남은 건 API 이름뿐 [정정: 2026-08-04 승격됐으나 이 표에 반영이 안 돼있던 걸 2026-08-06 뒤늦게 수정] |
## `research/` — 아직 착수 전, 상의 필요
@ -41,11 +42,19 @@
|---|---|---|
| `tween-plan.md` | 트윈을 Store 밖 특수 bind key로 처리, 기본 오버라이드는 Cancel | 중 — 세부 옵션만 남음 |
| `existing-instance-bind-plan.md` | 이미 생성된 인스턴스 재바인드 — 착수 안 하되 "미지원" 확정도 안 함, 열린 가능성 유지 | 하 — v2 초기 스코프 제외 |
| `component-composition-plan.md` | 컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, `StoreSource` 프록시 — 핵심 골격 수렴, modifier/Ref가 컴포넌트 경계를 어떻게 통과하는지만 남음 | 상 — 사용자가 "가장 문제되는 부분"으로 직접 지목 |
| `debug-tooling-plan.md` | 실물 Instance→코드 위치 역추적 Studio 플러그인(`quad-debug`) — 채널 실현 가능성(BindableEvent/Function 크로스 컨텍스트)까지 실측 검증 완료, 세부 API 이름·구현만 남음 | 하 — 사용자가 "quad 개발 완료 전엔 착수 못 함"으로 직접 후순위 지정, base 설계 시 훅 확장 지점만 고려 |
| `documentation-plan.md` | UI 네이밍 컨벤션 문서 + Store 부작용을 게임 시스템에서 깔끔하게 쓰는 패턴 문서 — 뼈대만, `debug-tooling-plan.md` 논의에서 파생 | 하 — 착수 시점 미정, 뼈대만 기록해둔 상태 |
| `documentation-plan.md` | 문서 사이트 구조(초심자/api/심화/`quadnomicon` 4축, 백엔드별 트랙 분리) + UI 네이밍 컨벤션·Store 부작용 패턴·권장 이벤트 핸들링 3개 세부 문서 뼈대 | 하 — 착수 시점 미정, 구조/스코프만 합의된 상태 |
| `documentation-content-map.md` | 위 4축에 실제로 뭘 채울지 `base/` 전체를 초심자/api/심화/skip으로 서베이한 콘텐츠 맵 — 초심자 core loop 목차 초안 포함 | 하 — 문서화 착수 시점의 목차/우선순위표로 쓸 것 |
| `framework-comparison-findings.md` | quad vs Fusion/Vide/react-lua 정직한 비교(실 소스 근거) — quad 강점, 진짜 불리한 점 중 고칠 만한 것 3개, 못 고치는 트레이드오프 정리 | 하 — 사용자 검토 후 반영 여부 결정 대기 |
| `ui-shorthand-plan.md` | UICorner/UIPadding/UIScale 인라인 편의 키(v1 `Corner`/`PaddingAll`/`Scale` 선례) — 여전히 필요한 기능으로 재확정(RoundSize만 네이티브 UICorner로 대체돼 불필요), 메커니즘(Handler)·패키지 배치(quad-roblox 코어) 확정 | 하 — 결론 남, M10 전후 구현하면 됨 |
| `additional-primitives-plan.md` | 확정 프리미티브(Source/State/Store/Ref/Observer/Modifier/Slot/DI)만으로 충분한지 웹 프레임워크·Fusion/Vide/v1 소스 근거로 조사 — 키 기반 동적 컬렉션 재조정(Fusion `ForPairs`/Vide `indexes()`류)이 가장 명확한 빈 자리로 확인, Effect/Batch/Context는 부차적 후보 | 상 — 사용자 판단 대기, 착수 전 M0/M1 스코프에 영향 줄 수 있음 |
| `pre-implementation-audit.md` | M0 착수 직전 크리티컬 감사(2026-08-06 신설) — `base/` 전체를 모호성/지연결정리스크/단순화후보 세 렌즈로 재검토, 11개 우선순위1(M0~M4 착수 전 확인 권장) + 11개 우선순위2 + 2개 단순화후보 | 상 — M0 착수 전 최소 우선순위1 항목 확인 권장 |
## `archive/` — 완료됐거나 완전히 뒤집힌 것, 능동 참고 불필요
| 문서 | 내용 |
|---|---|
| `store-source-proxy-reversed.md` | 2026-08-04에 확정했던 `StoreSource` 프록시 설계(Store가 Source를 감춘 별도 프록시로 감쌈) — 2026-08-06 세 번째 세션에서 "Source가 State를 구조적으로 만족" 재구성으로 완전히 대체됨. 원문·역전 이유·신구 비교표 보존, `quadnomicon` 소재 후보 |
## 참고

View file

@ -0,0 +1,73 @@
# [역전됨] `StoreSource` 프록시 설계 — Source가 State를 만족하는 서브타입으로 대체됨
**역전 일시**: 2026-08-06 (세 번째 세션). **원 확정 일시**: 2026-08-04
(`component-composition-plan.md` 컴포넌트화 논의 3~4번 항목).
**현재 유효한 설계**: `base/store-semantics.md`의 "Source가 State를
만족함 — 구조적 서브타입" 절이 최종 소스. 이 파일은 더 이상 능동적으로
참고할 필요 없음(구현에 안 씀) — 왜 이 경로를 갔다가 되돌아왔는지가
`quadnomicon`(프레임워크 설계자용 심화 콘텐츠, `research/documentation-plan.md`
0번 항목) 소재로 가치 있어서 사유·원문을 통째로 보존해둔 것.
## 역전된 사례 — 원래 무엇을 확정했었나 (2026-08-04 원문)
`store.key`로 값을 얻을 때, Store가 내부 Source 객체를 **직접 노출하지
않고** 별도의 얇은 프록시 타입 `StoreSource`를 만들어 반환하는 설계였음:
> **Source = 인터페이스이자 구현체**: 독립 생성자 `Source(initial)`가 기본
> 구현체, `store:GetSource("key")`(가칭)류 접근자가 반환하는 값은 같은
> 인터페이스를 구현하는 별도의 얇은 프록시(`StoreSource`) — 읽기는
> `store.key`로, 쓰기는 `store.key = v`로 위임. **내부 Source 객체를 그대로
> 노출하지 않음** — 그러면 "쓰기는 오직 Store의 `__newindex`뿐"이라는 기존
> 확정과 새 쓰기 경로가 충돌하게 됨.
>
> **캐시 안 함**: State가 이미 "매번 새로 만듦, store에 캐시 안 됨"으로
> 확정돼 있어 일관성 + 엔지니어링 비용 둘 다 이쪽이 쌈 — **사용자 확정**
> ("그냥 엔지니어링적으로 비용이 싼거 택해").
같은 논의에서 파생된 핸들러 계약 쪽 결정도 같이 뒤집힘 — Source를 핸들러가
직접 받을 때는 별도 유니온 타입으로 처리하기로 했었음:
> 핸들러가 값을 받을 때 `Source<T> | State<T>` 유니온으로 받고, 내부에서
> 타입 체크만 하면 됨(Source면 인스턴스 변경 이벤트에 걸어 역방향 쓰기까지
> 처리, State면 읽기만) — `isHandlable`/`priority`/`process`/`retract` 4종
> 계약에 5번째 항목을 추가할 필요 없음.
## 역전된 이유
`store.key`의 타입 문제를 다시 들여다보다가 드러남: Store의 정적 타입을
`{key: State<number>}`류 평범한 레코드 타입으로 지으면(2026-08-04 3차
라운드에서 확정했던 방식) Luau 구조적 타이핑상 그 필드의 읽기/쓰기 타입이
같아야 하는데, 실제 쓰기(`store.key = value`, raw `number`)와 읽기
(`State<number>`)가 서로 다른 타입이라 애초부터 정합적이지 않았음 —
`StoreSource` 프록시 설계 시점엔 이 비대칭을 못 잡았던 것.
이걸 풀려고 대안(store를 `store.key`/`store.state.key`로 네임스페이스
분리하는 안, `RefSource<T>`라는 store 전용 타입을 새로 만드는 안)을
검토하다가, 더 근본적인 재구성으로 수렴: **Source 자체가 구조적으로
State를 만족**(Svelte `Writable<T> extends Readable<T>`와 같은 모양)
하게 만들면,애초에 "Source를 감추고 별도 프록시로 감쌀" 이유 자체가
없어짐 — Store가 내부에 갖고 있는 진짜 Source 객체를 그대로 돌려줘도
안전하고, 오히려 프록시 객체를 매번 만들거나 캐싱하는 계층 하나가 통째로
사라져서 더 쌈.
## 이전 것과 지금 것의 차이
| | `StoreSource`(역전됨) | Source가 State를 만족(현재) |
|---|---|---|
| `store.key`가 반환하는 것 | 별도 프록시 `StoreSource`(Source 인터페이스를 구현한 wrapper) | 진짜 `Source<T>` 객체 그대로 |
| 쓰기 문법 | `store.key = value`(`__newindex`) | `store.key:Set(value)` |
| 캐싱 | "매번 새로 만듦"(State와 같은 정책) | Store 생성 시 이미 만들어둔 Source를 그대로 반환 — 별도 캐싱 메커니즘 자체가 불필요 |
| 핸들러가 Source를 받는 방법 | `Source<T> \| State<T>` 명시적 유니온 | `State<T>` 하나만 받아도 서브타입 호환으로 자동 통과, 런타임에 구분하고 싶으면 `isSource`류 판별자 |
| 타입 정합성 | 레코드 필드 읽기/쓰기 타입 비대칭 문제가 잠재해 있었음(발견 안 된 채로 확정됐었음) | 필드 타입이 항상 `Source<T>`로 대칭 — 쓰기가 메소드 호출로 옮겨가며 문제 자체가 해소됨 |
## 왜 완전히 헛수고는 아니었나
`StoreSource`가 짚었던 문제의식(Store 내부 표현을 그대로 노출하면 안
될 수 있다, Source와 State는 다른 쓰기 권한을 가져야 한다)은 여전히
유효함 — "State/Source 경계 규칙: 파생이면 읽기전용, 원본이면 쓰기 가능"
원칙(`base/component-composition-plan.md` 2번)은 살아남았고, 결론만
"별도 프록시 타입을 만든다"에서 "Source 자체를 서브타입으로 승격한다"로
바뀐 것. 이 반전 자체가 "타입이 없던 v1 습관을 재검토 없이 typed 재작성에
그대로 가져오면 안 된다"는 더 큰 교훈의 구체적 사례이기도 함(사용자 회고,
`base/store-semantics.md` 참고) — `quadnomicon`에서 "설계가 왜 이렇게
반전됐는가" 사례로 쓰기 좋음.

View file

@ -124,7 +124,7 @@ quad/
│ └── src/
│ ├── Source.luau # 값의 근원, 단일 지점
│ ├── State.luau # 캐시만 하는 non-owning 핸들, state(state) 분기
│ ├── Store.luau # source 집합체, dot-access, __newindex
│ ├── Store.luau # source 집합체, dot-access로 Source 그대로 반환
│ ├── Dispatch/
│ │ ├── init.luau # process/retract 엔진, isHandlable 우선순위 스캔
│ │ ├── Handler.luau # 핸들러 계약 타입(isHandlable/priority/process/retract)
@ -132,7 +132,7 @@ quad/
│ │ └── Slot.luau # add/remove/clear 재조정 로직(추상 자식 참조 기준)
│ ├── LifetimeHandle.luau # Connected 계산 속성 "인터페이스"(타입/계약만)
│ ├── PerInstanceState.luau # per-instance 상태 저장 "인터페이스"
│ ├── Ref.luau # CreatedRef 메커니즘(숫자 슬롯 참가자)
│ ├── Ref.luau # 범용 값 박스(.Value/:Wait()/콜백) + 그 위에 얹힌 CreatedRef(숫자 슬롯 참가자) 특수화
│ └── init.luau
└── quad-roblox/
├── wally.toml
@ -190,15 +190,20 @@ CollectionService 태그 등)를 흉내낼 필요가 없고, quad-base 자체
## Store/State/Source 온톨로지 — 확정됨 (요약)
Store는 source(실제 값이 존재하는 단일 지점) 집합체이고, `store.key`로 접근할
때마다 그 source를 감싸는 새 State(자기 고유 value 없는 조합 가능한 캐시)를
반환한다. 전파는 push-invalidate(신호만)/pull-recompute(`Get()` 시점) —
Fusion식 eager 노드 없이도 다이아몬드 의존성 중복 재계산 문제가 풀림. State는
쓰기 대상이 아니고(값 쓰기는 항상 Store의 `__newindex`), 값 하나만 다룰 땐
Store와 별개인 가벼운 `Source` 프리미티브를 씀. `store.key` dot-access를 타입
추론 1급 경로로 삼는 것도 3차 라운드에서 정식 확정됨 — **더 이상 열린 질문
아님**, 남은 건 정확한 API 이름뿐. 상세는 `base/bind-system-plan.md`
"Store/State/Source 온톨로지" 절 참고.
Store는 source(실제 값이 존재하는 단일 지점) 집합체이고, `store.key`
접근하면 Store 생성 시 이미 만들어둔 그 Source 객체를 그대로 반환한다
(별도 wrapper 없음 — **[2026-08-06 후속 세션 정정]** 원래 "매번 새 State를
감싸 반환"이었으나, `Source`가 구조적으로 `State`를 만족하도록 재구성되며
wrapper 계층 자체가 불필요해짐). 전파는 push-invalidate(신호만)/
pull-recompute(`Get()` 시점) — Fusion식 eager 노드 없이도 다이아몬드
의존성 중복 재계산 문제가 풀림. State는 쓰기 대상이 아니고, 값을 쓰는
경로는 `source:Set(value)`(Source가 State보다 넓은 인터페이스를 가짐 —
`.value`/`:Get()`/`:With`/`:Compute` 위에 `:Set`/`:Emit` 추가). 값 하나만
다룰 땐 Store와 별개인 가벼운 `Source` 프리미티브를 독립적으로도 씀.
`store.key` dot-access를 타입 추론 1급 경로로 삼는 것도 3차 라운드에서
정식 확정됨 — **더 이상 열린 질문 아님**, 남은 건 정확한 API 이름뿐.
상세는 `base/store-semantics.md`의 "Source가 State를 만족함" 절과
`base/bind-system-plan.md`의 "Store/State/Source 온톨로지" 절 참고.
## 아직 미정 (research/로 분리됨)

View file

@ -528,11 +528,16 @@ read ...`처럼, State끼리 자유롭게 합성/파이핑 가능한 것이 최
생성자 이름뿐(구현 단계). `base/store-semantics.md`의 "State 프리미티브는
실제로 필요하다" 정정에서 이어짐.
**핵심 온톨로지** (변경 없음):
**핵심 온톨로지** (2026-08-06 후속 세션에서 Store/Source 부분 정정 —
아래 "State는 쓰기 대상이 아님" 절 이후 내용 및 `base/store-semantics.md`
"Source가 State를 만족함" 절 참고):
- **Source** — 실제 값이 존재하고 변경될 수 있는 단일 지점(v1의 "값의 근원").
- **Store** — source들의 집합체. `store.a`처럼 키로 접근하면 그 source를
감싼 **새 State**를 매번 만들어 반환(state가 store에 캐시되어 재사용되는
게 아님 — source만 store에 귀속된 유일한 실체).
**구조적으로 State를 만족(단방향 호환)**`.value`/`:Get()`/`:With`/`:Compute`
전부 지원 위에 `:Set(value)`/`:Emit()` 추가.
- **Store** — Source들의 이름 붙은 모음, 그 이상 아님. `store.a`처럼 키로
접근하면 Store 생성 시 이미 만들어둔 **그 Source를 그대로 반환**(더 이상
별도 State wrapper를 매번 만들거나 따로 캐싱하지 않음 — Source 자체가
이미 State를 만족하므로 wrapper 계층 자체가 불필요해짐).
- **State** — source(또는 다른 state)의 결과를 캐싱만 하는 존재, 자기 고유의
독립적 value 개념이 없음. `state(state)`로 기존 state의 결과를 받아 새
state를 만들어 분기 가능 — 이게 사실상 Unix 파이프 영감의 "State끼리
@ -574,6 +579,41 @@ Modifier)에도 그대로 적용됨 — Modifier의 getter가 State 필드를
체이닝")과 이 원칙이 충돌하지 않는 이유가 바로 이것 — clone은 그저 참조
복사라 State 필드는 클론 이후에도 여전히 살아있는 lazy 핸들로 남음.
**왜 State 체인을 Modifier처럼 플래튼하지 않는가 (2026-08-06 후속 세션)**
**문제 제기(사용자)**: State가 `a → b → c`처럼 계속 연결되는 구조면, 이전
노드가 다음 노드에 대한 emit 연결/값 연결을 항상 들고 있어야 함(weak
table로 GC는 되지만 별도 데이터스트럭처 관리 부담). 대안으로, 각 State가
자기 Compute 함수 목록을 통째로 누적해서 갖고(Modifier의 clone-then-return
체이닝처럼) 매번 클론+append하면 링크드 그래프 자체가 필요 없어지지
않는가?
**기각 이유 — State의 정의 자체가 "캐싱하는 존재"임.** 위 온톨로지에
"State — source(또는 다른 state)의 결과를 **캐싱만 하는** 존재"라고
확정돼 있고, `previous` 두 번째 인자 메커니즘(무거운 파생 엔진 객체
재생성 비용 절감)도 이 캐싱 전제 위에서만 의미가 있음. 만약 Compute
체인을 매번 통째로 클론해 각 leaf가 독립된 함수 목록을 갖게 하면, 중간
State를 여러 갈래가 공유하는 다이아몬드 형태(`b`에서 `c1 = b:Compute(g1)`,
`c2 = b:Compute(g2)`로 분기)에서 `b`까지의 계산이 캐시 공유 없이 소비자
수만큼 중복 실행됨 — `previous` 메커니즘이 막으려던 문제를 반대로 다시
만들어내는 셈이라 방향이 안 맞음.
**"별도 데이터스트럭처 관리" 부담은 실제로는 작음.** "관측해야
실체화된다" 원칙 때문에 살아있는 노드-대-노드 구독 엣지가 필요한 건
실제로 관측되는(`Get()`되는) State뿐 — 중간에 만들어놓고 아무도 안 보는
State는 구독 등록 자체가 안 일어남. 다이아몬드에서 중복 워크를 막는
`invalid` 플래그 dedup 장치도 체인 전체가 링크드일 것을 요구하지 않고
각 노드가 자기 구독자 목록만 가지면 되는 것이라, 이 결정과 무관하게
그대로 유지됨. 구현은 Observer와 동일한 패턴(외부 weak table,
`{[child] = true}` 류)으로 충분 — 새 메커니즘 발명 아님.
**결론**: 노드별 캐시 유지(현재 모델) 유지, 플래튼 기각. Modifier가
플래튼+클론을 쓰는 건 애초에 캐싱이 필요 없는 정적 데이터라 성립하는
것이고, State는 존재 이유 자체(캐싱)가 달라 같은 패턴을 적용할 수 없음.
`research/documentation-plan.md`의 심화 문서 후보로 남겨둠 — "왜 State는
Modifier처럼 플래튼하지 않는가"는 설계 근거를 알고 싶은 사용자를 위한
좋은 심화 콘텐츠 소재.
**`:With`/`:Compute` — self 인자도 lazy 핸들로 통일**
- 최초안(self 값은 포지셔널 raw 값, with한 값만 클로저로 읽음)에는 실제
@ -595,11 +635,17 @@ Modifier)에도 그대로 적용됨 — Modifier의 getter가 State 필드를
**State는 쓰기 대상이 아님 — 확정, Source는 독립 공개 프리미티브로 격상**
- `.value`는 항상 읽기 전용. 값을 쓰는 경로는 오직 Store의 `__newindex`
(`store.key = value`, 이미 확정된 문법)뿐 — State에는 대응하는 쓰기 API가
아예 없음. "State에 `.value = x`를 허용하면 다른 source에서 파생된
state에 직접 쓰기가 가능해져 버린다"는 이전 우려는 이걸로 근본적으로
해소(그런 API 자체가 없음).
- `.value`는 항상 읽기 전용. State에는 쓰기 API가 아예 없음. "State에
`.value = x`를 허용하면 다른 source에서 파생된 state에 직접 쓰기가
가능해져 버린다"는 이전 우려는 이걸로 근본적으로 해소(그런 API 자체가
없음).
- **[정정, 2026-08-06 후속 세션] 값을 쓰는 경로는 `store.key = value`
(`__newindex`)가 아니라 `store.key:Set(value)`로 전환됨** — 이유와
상세는 `base/store-semantics.md`의 "Store 값 설정 문법" 절 참고(요지:
Source가 State를 만족하는 구조로 바뀌며 레코드 타입 읽기/쓰기 대칭을
맞추려면 대입 문법을 포기해야 함 + `=`가 암시하는 "즉시 커밋"이 실제
lazy 동작과 정서적으로 안 맞는다는 논거). 같은 문서의 "Source가 State를
만족함" 절에 Source/State 서브타입 구조 전체가 정리돼 있음.
- **`Source`는 Store의 내부 구현 디테일이 아니라 별도의 가벼운 공개
프리미티브로 노출** — Store는 다수의 source를 등록/관리하는 무거운
구조라, 값 하나만 반응형으로 다루고 싶을 때 Store를 통째로 만드는 건
@ -632,10 +678,15 @@ Modifier)에도 그대로 적용됨 — Modifier의 getter가 State 필드를
- `store "key"`(문자열 커링)로 `state<T>`를 오버로드 함수 타입으로 정확히
추론하려는 시도는 포기하고, **`store.key`(dot-access)를 1급 경로로 확정**
— Store 타입을 `{key: State<number>, other: State<string>}`류 평범한
— Store 타입을 `{key: Source<number>, other: Source<string>}`류 평범한
레코드 타입으로 지으면 일반 구조적 필드 타이핑으로 자동 해결되고, 문자열
리터럴 narrowing 문제 자체가 안 생김. `store "key"` 문자열 커링은 동적
키가 필요할 때 쓰는 미타입(`State<any>`) 폴백으로 격하.
리터럴 narrowing 문제 자체가 안 생김([정정, 2026-08-06] 원래 `State<T>`
필드로 적혀있었으나 Source가 State를 만족하는 구조로 바뀌며 `Source<T>`
갱신 — `store.key = value` 쓰기 문법이 `:Set()`으로 옮겨가 이 필드가
더 이상 `__newindex`로 쓰이지 않으므로 읽기/쓰기 타입 대칭 문제도 같이
해소됨, `base/store-semantics.md` "Source가 State를 만족함" 절 참고).
`store "key"` 문자열 커링은 동적 키가 필요할 때 쓰는 미타입(`Source<any>`)
폴백으로 격하.
- 이 패턴은 Store에만 국한되지 않고 **인스턴스 생성까지 관통하는 프로젝트
전역 관습으로 확정**됨 — 단 이벤트는 이후 4차 라운드에서 이 관습의
**유일한 예외**로 빠졌음(PA님 방식인 문자열 키+런타임 리플렉션으로 전환).
@ -821,9 +872,11 @@ Service` 기반으로 구현)로 두면 됨 — 별도 `On` 모듈/필드 접근
**Store 쪽 dot-access는 그대로 유지**: `store.key`(1급 타입 경로)/
`store "key"`(문자열 커링, 동적 키 폴백)는 이벤트와 달리 실질적으로 Luau가
타입을 좁혀주는 이득이 있어서(Store 자체가 `{key: State<number>, ...}`
평범한 레코드 타입으로 지어짐) 그대로 유지 — 이벤트만 예외였을 뿐, "정적으로
알려진 것=필드 접근" 원칙 자체가 깨진 건 아님.
타입을 좁혀주는 이득이 있어서(Store 자체가 `{key: Source<number>, ...}`
평범한 레코드 타입으로 지어짐[정정: 2026-08-06 후속 세션에서 필드 타입이
`State<T>`→`Source<T>`로 갱신, "Source가 State를 만족함" 절 참고]) 그대로
유지 — 이벤트만 예외였을 뿐, "정적으로 알려진 것=필드 접근" 원칙 자체가
깨진 건 아님.
**PA님 코드와 대조해서 재확인한 것(변경 없음)**:
- **OOP 회피 결정은 오히려 보강됨** — PA님의 `ObjectOrientedProgramming/

View file

@ -52,33 +52,50 @@ Vue/Svelte 대비 내세우는 강점과 동일 논리) — **사용자 확정**
### 2. State/Source 경계 규칙: 파생이면 읽기전용, 원본이면 쓰기 가능
State는 `:With`/`:Compute`로 만들어진 파생값일 수 있어 쓰기가 정의 자체가
안 됨. Source(독립이든 Store 소속 `StoreSource` 프록시든)는 파생이 아니라
항상 원본 슬롯 하나를 직접 가리키므로 쓰기가 의미 있음 — **사용자 확정**
("맞음. 확실해").
안 됨. Source는 파생이 아니라 항상 원본 슬롯 하나를 직접 가리키므로 쓰기가
의미 있음 — **사용자 확정**("맞음. 확실해"). 이 원칙 자체는 그대로 유지되고,
아래 3번의 구체적 메커니즘만 2026-08-06 후속 세션에서 더 단순하게 갱신됨.
### 3. `StoreSource`: Source를 인터페이스+구현체로 두고, Store 키에서 그 인터페이스를 구현하는 얇은 프록시를 받음
### 3. [정정, 2026-08-06 후속 세션] `StoreSource` 프록시 개념 폐기 — Source가 State를 구조적으로 만족하므로 Store가 내부 Source를 그대로 반환
- **Source = 인터페이스이자 구현체**: 독립 생성자 `Source(initial)`가 기본
구현체, `store:GetSource("key")`(가칭)류 접근자가 반환하는 값은 같은
인터페이스를 구현하는 별도의 얇은 프록시(`StoreSource`) — 읽기는
`store.key`로, 쓰기는 `store.key = v`로 위임. **내부 Source 객체를 그대로
노출하지 않음** — 그러면 "쓰기는 오직 Store의 `__newindex`뿐"이라는 기존
확정과 새 쓰기 경로가 충돌하게 됨.
- **캐시 안 함**: State가 이미 "매번 새로 만듦, store에 캐시 안 됨"으로
확정돼 있어 일관성 + 엔지니어링 비용 둘 다 이쪽이 쌈 — **사용자 확정**
("그냥 엔지니어링적으로 비용이 싼거 택해").
**원래 이 절은 "Source를 인터페이스+구현체로 두고 Store 키에서 얇은 프록시
(`StoreSource`)를 받는다"는 방향이었음 — 지금은 폐기됨.** 이후 세션에서
Store/Source dot-access 타입 문제(레코드 타입의 읽기/쓰기 비대칭)를
다루다가 더 근본적인 재구성으로 수렴: **`Source<T>`가 구조적으로
`State<T>`를 만족**(단방향 호환, Svelte `Writable<T> extends Readable<T>`
같은 모양)하도록 만들면, Store가 "내부 Source를 감추고 별도 프록시를
새로 만들어 노출"할 이유 자체가 없어짐 — `store.key`가 Store 생성 시
이미 만들어둔 진짜 Source 객체를 그대로 돌려줘도 안전함(Source 자체가
이미 State의 읽기 계약을 전부 만족하고, 거기에 `:Set(value)`/`:Emit()`이
추가로 있을 뿐이라 "원본이라 쓰기 가능"이라는 위 2번 규칙과도 자연히
맞아떨어짐). 상세 근거·타입 설계·Luau 솔버 검증 필요 항목은
`base/store-semantics.md`의 "Source가 State를 만족함" 절이 최종 소스 —
이 문서는 배경만 유지.
### 4. Source 직접 전달(양방향)은 핸들러 계약 확장 없이 타입 유니온으로 처리 — 단, 실사용 범위는 좁음
- **쓰기 문법도 같이 바뀜**: `store.key = v`가 아니라 `store.key:Set(v)`
(레코드 타입 읽기/쓰기 대칭 + lazy 동작에 `=`가 암시하는 "즉시 커밋"이
안 맞는다는 논거, 같은 절 참고).
- **캐시 문제도 이걸로 자연히 해소**: State를 "매번 새로 만듦"이던 이전
모델과 달리, 이제 Store는 생성 시 만들어둔 Source를 그대로 갖고 있다가
돌려주기만 하므로 별도 캐싱 메커니즘 자체가 불필요(래퍼 생성 단계가
아예 없어짐 — 이전보다 더 쌈).
- 핸들러가 값을 받을 때 `Source<T> | State<T>` 유니온으로 받고, 내부에서
타입 체크만 하면 됨(Source면 인스턴스 변경 이벤트에 걸어 역방향 쓰기까지
처리, State면 읽기만) — `isHandlable`/`priority`/`process`/`retract` 4종
계약에 5번째 항목을 추가할 필요 없음. Source 자체가 계산이 없는 원천이라
가능한 단순화 — **사용자 확정**("그냥 타입 상 source를 받거나 state를
받거나 하면 됨. source 자체는 원천이라 컴퓨팅 같은거 없어").
- **하지만 실사용은 좁을 것으로 예상**: `isEnabled`처럼 여러 조건에 영향
받는(=파생된) 값은 애초에 State지 Source가 아니므로 이 경로로 못 넘김.
즉 Source 직접 전달이 통하는 건 진짜 단순한 1:1 원본-토글 케이스뿐이고,
### 4. [정정, 2026-08-06 후속 세션] Source 직접 전달 — 타입 유니온도 불필요해짐
원래 "핸들러가 `Source<T> | State<T>` 유니온으로 받는다"는 방향이었으나,
Source가 State를 구조적으로 만족하는 지금은 **유니온 자체가 필요 없음**
핸들러는 그냥 `State<T>` 하나만 받아도 Source 인스턴스가 자동으로 그
자리에 들어감(서브타입 호환). `isHandlable`/`priority`/`process`/`retract`
4종 계약에 5번째 항목을 추가할 필요 없다는 결론은 그대로 유지, 다만 근거가
"타입 유니온으로 처리"에서 "서브타입이라 유니온 자체가 불필요"로 더
단순해짐. 단, 핸들러가 "이거 Source면 역방향 쓰기까지 걸고 싶다"처럼
**런타임에** Source인지 구분하고 싶은 경우는 여전히 있을 수 있음 —
그건 타입 유니온이 아니라 런타임 판별자(`isSource`류, `isObserver`
패턴과 동일한 결)로 처리하면 됨.
- **실사용 범위가 좁다는 판단은 그대로 유지**: `isEnabled`처럼 여러 조건에
영향받는(=파생된) 값은 애초에 State지 Source가 아니므로 이 경로로 못
넘김. Source 직접 전달이 통하는 건 진짜 단순한 1:1 원본-토글 케이스뿐이고,
일반적인 경우엔 React식 `value(State) + onChange(callback)` 패턴이 기본
**사용자 확정**("isenabled가 여러 조건에 영향 받으면 바로 문제가
생기는거지. 따라서 실제 사용은 제한적일듯. callback을 쓰는게
@ -250,10 +267,12 @@ Ref를 받으면 그냥 전부 실행하면 됨, `modifier-plan.md` §4-2) —
## 남은 열린 질문 (`.claude/question.md`에도 취합, 전부 후순위 — 이름만 남음)
- **정확한 API 이름**: `Component`(플레인 함수 규약이라 별도 래퍼가 필요한지
자체도 불확실 — 아마 불필요), `GetSource` 계열 접근자 이름, `Source`
독립 생성자 이름, 컴포넌트 경계용 `props.Modifier`/`props.Ref` 필드명,
`Modifier.Merge` 함수명은 전부 가칭. `base/bind-system-plan.md`의 "남은
열린 질문" 절(정확한 함수/생성자 이름 미정)과 같은 급의 후순위 항목 —
구현 단계에서 다른 이름들과 함께 확정.
자체도 불확실 — 아마 불필요), `Source`/`State` 독립 생성자·타입 이름,
컴포넌트 경계용 `props.Modifier`/`props.Ref` 필드명, `Modifier.Merge`
함수명은 전부 가칭. (`GetSource` 계열 접근자는 위 3번 정정으로 아예
불필요해짐 — `store.key`가 직접 Source를 반환하므로 별도 접근자 자체가
없음.) `base/bind-system-plan.md`의 "남은 열린 질문" 절(정확한 함수/
생성자 이름 미정)과 같은 급의 후순위 항목 — 구현 단계에서 다른
이름들과 함께 확정.
- **`quad2-try`는 확인 불필요로 재확인** — 진행이 중단된 상태라 이 논의와
무관.

View file

@ -54,9 +54,11 @@ pull-recompute)·`:Compute` 인자 규칙·State 쓰기 금지·`Source` 독립
해주지만, 그 결과를 다시 분기하고 싶으면(하나의 파생 스트림에서 여러
소비자가 각자 다른 추가 compute를 얹고 싶은 경우) `state(state)`처럼 기존
state의 결과를 받아 새 state를 만드는 조합이 필요.
- **store에서 state를 얻는 연산(예: `store "key"`)은 항상 새 state 인스턴스를
반환한다** — state 자체가 캐시되어 재사용되는 게 아니라, source만 store에
귀속된 유일한 실체이고 그 위의 state는 매번 새로 생성됨.
- **[정정, 2026-08-06 후속 세션] store에서 값을 얻는 연산(`store.key`)은
Source를 직접 반환한다 — 더 이상 별도 State 인스턴스를 감싸서 반환하지
않음.** 상세는 아래 "Source가 State를 만족함" 절 참고. 이 항목의 원래
버전("항상 새 state 인스턴스를 반환")은 틀림 — Store가 별도 wrapper
없이 자기 안에 만들어둔 Source를 그대로 돌려주는 쪽으로 재정리됨.
- 이건 quad2-try(폐기된 이전 시도)의 `Pipe` copy-on-write 절충안을 대체하는
방향으로 좁혀짐 — 별도 `Pipe` 타입을 만들어 소유권/버전 가드를 넣는 대신
State 자체가 "파이핑 결합체"이고 `state(state)`로 분기하면 될 걸로 보임
@ -93,29 +95,115 @@ pull-recompute)·`:Compute` 인자 규칙·State 쓰기 금지·`Source` 독립
추론 문제는 `store.key`(dot-access)를 1급 경로로 확정하며 해소(같은 문서
"타입 추론 문제" 절, 3차 라운드).
## Store 값 설정 문법 — v1 인체공학 유지 (확정)
## Source가 State를 만족함 — 구조적 서브타입, RefSource 개념 폐기 (2026-08-06 후속 세션)
**사용자 확인 완료**: Store 값 설정은 `__newindex` 기반(`myStore.key = value`)을
그대로 유지 — ProfileService 등 Roblox 생태계에서 이미 익숙한 관용구라 바꿀
이유 없음. 마찬가지로 다음 두 인체공학도 유지:
**배경**: `store.key`가 매번 새 State를 감싸 반환하던 이전 모델의 타입
문제(레코드 타입 `{key: State<number>}`가 읽기/쓰기 비대칭이라 Luau
타이핑이 안 맞음, 위 "Source가 State를 만족함" 논의에서 도출)를 풀다가
사용자가 제안한 더 근본적인 재구성. `RefSource<T>`(store 슬롯을 가리키는
전용 타입)를 따로 만드는 중간안도 검토했으나, 최종적으로 **Source 자체가
State를 만족하도록 만들고, RefSource라는 별도 타입은 폐기**하는 쪽으로
수렴.
- **괄호 생략(paren-less) 구조** — 필요 시 커링(`myStore "key"`처럼 문자열
하나로 register를 얻는 v1 스타일)을 계속 허용.
- **`:` 체이닝** — 값을 바꾸는 연산에 한해 체이닝 문법 허용(`base/
architecture.md`의 "함수지향 디폴트, `:`는 예외적으로만" 원칙과 일치 — 체이닝이
자연스러운 곳 중 하나가 바로 이 store 값 변경).
**확정 방향**:
- **`Source<T>`가 구조적으로 `State<T>`를 만족(단방향 호환)** — State
자리엔 Source를 넣을 수 있지만 역은 안 됨(Svelte의 `Writable<T> extends
Readable<T>`와 같은 모양). Source는 State가 주는 모든 것(`.value`,
`:Get()`, `:With(...)`, `:Compute(fn)`) 위에 `:Set(value)`/`:Emit()`을
추가로 가짐.
- **`:With`/`:Compute`는 Source에서도 항상 `State<U>`를 반환** — Source
자신을 변형하는 게 아니라, "Source의 State 뷰를 뽑아 그 위에 파이핑"하는
것과 동치. 구현은 metatable `__index` 델리게이션(Source의 메소드
테이블이 State의 메소드 테이블로 폴백)으로 충분 — `Modifier`의 제네릭
`__index` 트릭(`base/modifier-plan.md`)과 같은 패턴이라 로직 중복이
생기지 않음.
- **`RefSource<T>` 같은 별도 타입은 불필요, `Store({defaults})`
내부적으로 `{[key] = Source(default), ...}`나 다름없게 됨** — `store.key`
Store 생성 시 이미 만들어둔 Source를 그대로 돌려줄 뿐, 매번 새로 만들거나
별도로 캐싱할 wrapper 객체 자체가 없음. 이전에 검토했던 "State를
weak table로 캐싱" 절충안보다 더 싸다(래퍼 생성/캐싱 단계 자체가
사라짐). v1이 모든 값을 Store 하나에 몰아넣던 습관은 "당시 정적 타입이
없어 단순하게 쓰는 게 편해서"였다는 게 사용자의 회고적 재평가 — 지금은
타입이 핵심 제약이라 그 전제 자체가 더 이상 안 맞고, 이번 정리로 Store는
"이름 붙은 Source 모음, 그 이상 아님"으로 더 단순해짐.
- **이 서브타입 관계는 `quad2-try`에서 기각한 컴포넌트/클래스 OOP 상속과는
다른 층위.** 그때 금지한 건 사용자가 짜는 컴포넌트 계층 구조(`Class:Extend()`류
매직)였고, 지금은 두 프리미티브 타입 사이의 구조적 서브타이핑(런타임
구현 델리게이션 포함)이라 그 금지와 충돌하지 않음.
- **동적 키 폴백(`store "key"`)은 이제 `State<any>`가 아니라 `Source<any>`
반환**하는 것으로 자연히 갱신됨(위 "타입 추론 문제" 절과 연동).
`base/architecture.md`의 "복사 구현 지양, 팩토리 함수로 대체" 원칙과 함께 읽을
것 — v1의 문제는 metatable 체이닝으로 매번 새 테이블을 할당하며 "불변 빌더"를
흉내낸 것이었지, `:` 체이닝 문법 자체나 커링 문법 자체가 아니었음. v2는 문법
인체공학(사용자가 좋아하는 부분)은 유지하되 내부 구현(체이닝이 아니라 팩토리
함수)만 바꾼다.
**검증 필요(확정 아님, M0 스파이크 대상)**: `Source<T>``:Compute`
시그니처가 자기 자신(`Source<T>`)과 `State<U>`를 동시에 참조하는 제네릭
메소드라, Luau 솔버가 재귀 타입 조합에서 막히지 않는지 실제로 검증
필요(사용자 우려: "솔버가 종종 죽는다"). 구분해서 볼 것:
- **자기 자신을 가리키는 self 타이핑**(`{ Compute: <U>(self: Source<T>, ...) -> State<U> }`
같은 패턴)은 Luau에서 극히 흔하고 대체로 안전 — 모든 메소드 테이블
클래스가 쓰는 패턴이라 이것 자체가 위험 신호는 아님.
- **진짜 위험한 건 두 제네릭 타입 별칭이 서로를 참조하는 상호 재귀**
(`Source<T>` 정의가 `State<T>`를 참조하고, `State<T>`도 거꾸로
`Source<T>`를 참조하는 경우) — 이게 Luau 솔버가 알려진 대로 취약한
패턴. **`State<T>``Source`를 전혀 참조하지 않도록 먼저 독립적으로
정의하고, `Source<T>``State<T>`를 참조하는 단방향 의존으로 두면**
이 위험한 패턴 자체를 피할 수 있어 보임 — 다만 이것도 추론이라 실제
Luau로 확인 전엔 확정 아님.
- 사용자는 `&`(교차 타입) 조합보다 **타입을 손으로 펼쳐 쓰는(flatten)
쪽을 선호**(엔지니어링 비용을 감수하더라도 솔버 안정성 우선) — 이건
런타임 구현의 델리게이션(위 항목)과는 별개 축이라 서로 충돌 안 함:
타입은 펼쳐 쓰고 구현은 공유하는 조합이 가능함.
- `ROADMAP.md` M0의 "Store/State propagation" 스파이크 항목에 이 구체적
케이스(Source가 State를 만족하는 제네릭 메소드 체이닝)를 포함해서
검증할 것.
## Store 값을 직접 mutate한 뒤 전파 — `:Emit(key)` (2026-08-06 후속 세션)
**이름 주의**: `Source`/`State`라는 이름 자체가 `CLAUDE.md` "지금 할 일"
2번의 용어 정리 대상(특히 `State`)과 겹침 — 구조(서브타입 관계, RefSource
폐기)는 지금 확정해도 정확한 이름은 용어 정리 라운드까지 가칭으로 남김.
## Store 값 설정 문법 — `myStore.key = value` 폐기, `source:Set(value)`로 전환 (2026-08-06 후속 세션, 정정)
**이전 버전("v1 인체공학 유지, `__newindex` 기반 `myStore.key = value`
그대로")은 폐기됨.** 아래 "Source가 State를 만족함" 절의 타입 설계와
맞물려 재검토된 결과:
1. **타입 대칭성**: `store.key`가 이제 `Source<T>`를 직접 반환하는
평범한 레코드 필드(`{key: Source<number>}`)로 타이핑되는데, 레코드
필드는 읽기/쓰기 타입이 같아야 Luau 구조적 타이핑이 깨끗하게 성립함.
`store.key = value`(raw `T` 대입)를 유지하면 읽기(`Source<T>`)/쓰기(`T`)
타입이 갈려 mismatch가 남음 — `store.key:Set(value)`로 통일하면 필드
타입이 항상 `Source<T>`로 대칭적이라 문제 자체가 안 생김(사용자 지적).
2. **의미론적 정직성**: `=` 대입 문법은 관례상 "그 자리에서 즉시 확정되는
부작용 없는 값 쓰기"를 암시하는데, quad의 실제 동작은 **lazy**
`Set`은 무효화 신호만 쏘고, 실제 재계산은 나중에 누군가 관측(`Get()`)할
때만 일어남("Emit으로 필요한 사람 있어? 하고 물어보고, 있어야 진짜
계산 시작"). 이건 `=`가 암시하는 "즉시 커밋"과 정서가 안 맞고, 메소드
호출(`:Set()`)이 "이건 프로세스를 트리거하는 연산"이라는 걸 더 정직하게
신호함(사용자 확정 논거).
3. `:Set()`은 이미 확정된 "값을 바꾸는 연산엔 `:` 체이닝 허용" 원칙(`base/
architecture.md`)에도 자연스럽게 들어맞음 — 문법 자체가 새로 생기는 게
아니라 기존 원칙의 정상적인 적용.
**남는 것**: `myStore "key"`(문자열 커링)는 이미 3차 라운드에서 동적 키
전용 미타입 폴백으로 격하돼 있었으므로 이번 정정과 무관하게 그대로 유지.
`:` 체이닝 원칙도 `:Set()` 자체가 그 사례라 유지.
`base/architecture.md`의 "복사 구현 지양, 팩토리 함수로 대체" 원칙과 함께
읽을 것 — v1의 문제는 metatable 체이닝으로 매번 새 테이블을 할당하며
"불변 빌더"를 흉내낸 것이었지, `:` 체이닝 문법 자체나 커링 문법 자체가
아니었음.
## Source 값을 직접 mutate한 뒤 전파 — `:Emit()` (2026-08-06 후속 세션, 호출부 정정)
**결정**: Source가 들고 있는 값을 새 값으로 교체하지 않고 제자리에서
mutate한 뒤, `Store:Emit(key)`로 그 key의 무효화 신호만 별도로 쏘는
것을 **Source 원천(store가 직접 들고 있는 값)에 한해 허용**한다.
mutate한 뒤, `:Emit()`으로 무효화 신호만 별도로 쏘는 것을 **Source
원천(store가 직접 들고 있는 값)에 한해 허용**한다.
**[정정, 같은 세션 후반]** 원래 `Store:Emit(key)`(Store에 key를 넘겨
호출)로 적혀있었으나, 아래 "Source가 State를 만족함" 절에서 `store.key`
자체가 Source를 직접 반환하는 것으로 바뀌면서 `Emit`도 Source의 평범한
메소드로 이동 — `store.key:Emit()`(key 인자 불필요, 이미 손에 든 Source
핸들에 바로 호출). `Store:Emit(key)`라는 별도 경로는 유지할 이유가
없어져 폐기(같은 걸 하는 두 번째 경로를 남기지 않는다는 이번 세션 전반의
원칙과 일치 — `store.key = value``store.key:Set(value)` 정리와 같은 결).
**존재 이유(우선순위순)**:
1. **clone이 아예 불가능한 값이 있음.** userdata나 외부 라이브러리
@ -153,8 +241,13 @@ mutation에 정식 신호를 붙여주는 것뿐.
정적 flatten으로 dispatch와 완전히 별개인 단계에서 처리되고
(`base/modifier-plan.md`), `State<Modifier>`가 UB로 확정된 것도 같은
이유(`modifier-plan.md` 7번) — Store/State/dispatch 경로엔 애초에
Modifier용 processor가 없음. 위 "하드 경계"와 같은 이유로, `Emit`
Modifier의 정적 flatten과 충돌할 걱정 자체가 성립하지 않음(둘이 만날
Modifier용 processor가 없음. **[2026-08-06 후속 세션 추가]** Source가
State를 구조적으로 만족하게 되면서 이 UB는 `Source<Modifier>`(Store를
거치지 않는 독립 `Source(someModifier)`)에도 동일하게 적용됨을 명시 —
Source가 State 계약을 만족하는 이상 같은 이유(Modifier용 processor
부재)가 그대로 적용되고, 별도로 다시 논증할 필요 없음. 위 "하드 경계"와
같은 이유로, `Emit`이 Modifier의 정적 flatten과 충돌할 걱정 자체가
성립하지 않음(둘이 만날
지점이 없음).
## 여러 스토어 값을 묶어 처리하는 것 (dependency array) — 확정

View file

@ -72,7 +72,39 @@ Fusion/Vide/quad v1 소스를 서브에이전트 2개로 병렬 조사 완료,
- `Store`/`Source`/`Modifier`/`Ref`/`process`/`retract`/`isHandlable`은
업계 선례와 잘 맞거나 이미 신중하게 결정된 이름들이라 특별한 문제 없음.
### 2. 낮은 우선순위
### 2. 구현 착수 직전 감사 결과 (2026-08-06 신설, M0 착수 전 확인 권장)
`research/pre-implementation-audit.md``base/` 전체를 M0 착수 직전
시점에서 모호성/지연결정리스크/단순화후보 세 렌즈로 재감사한 결과. 총
11개 우선순위1(구현 중 바로 부딪힐 가능성 높음) + 11개 우선순위2(지금
정해두면 싼 지연리스크) + 2개 단순화후보. 전체는 그 문서 참고, 특히
사용자 판단이 필요한 것 위주로 요약:
- **Tween.luau가 "범용 store-bind 캐치올 핸들러"의 유일한 예시로 서술됨**
일반 반응형 프로퍼티 바인딩(`BackgroundColor3 = store.color`, 애니메이션
없음)이 결국 이름은 "Tween"인 파일을 거쳐가는 건지, 아니면 별도 범용
`Handlers/StoreBind.luau`가 있어야 하는 건지 확정 필요 — 우선순위1-1.
- **`State<Modifier>` 타입 차단(엔지니어링 비용 감수)과 Ref/Slot이 Modifier
필드에 들어가는 건 UB 방치 — 같은 문서 안에서 정반대 원칙이 근거 설명
없이 나란히 적용됨.** 왜 이 경우만 예외로 방어하는지 명문화 필요, 또는
Luau에서 실제 타입 차단이 가능한지부터 확인(안 되면 그냥 UB로 격하) —
문서모순 절 + 우선순위2-2.
- **`props.Modifier`/`props.Ref` forwarding 관례가 Lua 배열 리터럴
nil-hole 함정에 그대로 노출됨** — caller가 Modifier/Ref를 안 넘기면
`{nil, ref, child}`에서 뒤 항목까지 통째로 무시될 수 있는 버그 클래스.
M0 스파이크에 이 케이스(안 넘기는 경우)를 반드시 포함시킬 것 — 우선순위1-5.
- **`canExecute`/`Connected`의 실제 구현 방식(Parent==nil vs Connection.
Connected vs Destroying 플래그)이 미확정인데 이미 Slot/Observer/store-bind
retract 전역에 재사용 확정됨** — M2/M3 착수 전 실측 필요 — 우선순위1-6.
- **`LifetimeHandle` 인터페이스가 M8에 배치돼 있지만 M4/M6이 이미 그걸
필요로 함(로드맵 순서 역전)** — quad-base 인터페이스 정의를 M2/M3로
옮기는 게 자연스러워 보임, `ROADMAP.md` 수정 필요 — 우선순위1-9.
- 그 외(Slot CRUD 의미론 미정의, retract 시 "이전 핸들러" 추적 책임 소재,
우선순위 스캔 동률/매치실패 처리, `:Compute``previous` 인자가
오버엔지니어링일 수 있음, UI shorthand의 기존 UICorner 매칭 기준 등)는
`pre-implementation-audit.md` 본문 참고.
### 3. 낮은 우선순위
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
착수를 막지 않음.

View file

@ -0,0 +1,174 @@
# 문서 콘텐츠 분류 맵 (초심자/api/심화/skip)
**상태**: research — `documentation-plan.md` 0번 항목(3축 구조: 초심자/api/심화
+ 백엔드별 트랙 분리)이 확정된 뒤, 실제로 각 축에 뭘 채울지 `.claude/base/*.md`
전체 + 관련 `research/*.md`(tween-plan, ui-shorthand-plan)를 2026-08-06 세션에
6개 에이전트로 병렬 서베이해 분류함. **아직 문서를 쓰라는 뜻 아님** — 착수
시점은 여전히 구현 우선(`CLAUDE.md` "지금 할 일" 1번). 나중에 실제 문서화를
시작할 때 이 맵을 목차/우선순위표로 쓰면 됨.
**api↔심화 연결 원칙(사용자 확정)**: api 문서는 항목마다 설명을 간략하게
유지하고, 근거·내부 동작까지 파고드는 내용은 심화 섹션으로 링크("더 알아보기
→ 심화")하는 방식으로 연결. 아래 [api] 항목 중 "→심화"가 붙은 것들이 이
패턴 대상.
**분류 기준**: [초심자] core loop에 필수(백엔드 구체적, quad-roblox 기준) /
[api] 레퍼런스, 빠른 룩업용 짧은 설명 / [심화] 왜 이렇게 설계했는지, 최적화·
대규모 코드베이스 관리 관심자용 / [skip] 내부 설계 과정 기록, 최종 사용자
문서엔 안 들어감(세션 날짜, 정정 이력, 조사 원자료 등).
---
## 1. 초심자(getting-started) core loop — 취합된 목차 초안
전체 서베이에서 나온 [초심자] 항목을 실제 학습 순서로 재배열한 것. 이대로
목차를 잡으면 좋아 보임(그대로 확정은 아니고 초안):
1. **초기화**`RobloxFactory(QuadBase)`로 base+backend 조립 (`module-lifecycle-plan.md`, `bind-system-plan.md`)
2. **Instance 만들기** — DOMless 즉시 생성 모델, 제네릭 `new<Class>` + 자주 쓰는 ~25개 클래스 정적 필드(`Frame`, `TextButton` 등) (`architecture.md`, `bind-system-plan.md`)
3. **속성 채우기**`[Attribute "Name"]`, `[Tag ""] = true` 특수 바인드 키 (`architecture.md`)
4. **반응형 기초**`Source`/`Store` 생성, `store.key`(dot-access)로 Source 읽기(Source는 State를 만족), `store.key:Set(value)`로 쓰기, State는 항상 읽기 전용 (`bind-system-plan.md`, `store-semantics.md`; 2026-08-06 후속 세션에서 dot-access가 Source를 직접 반환하고 쓰기가 `:Set()`으로 바뀜)
5. **스타일링** — Modifier 기본 체이닝(`:FontSize(14)`), 배열/인라인 merge 우선순위 규칙 (`modifier-plan.md`)
6. **자식 전달** — Slot 기본 개념(children 배열, add/remove/clear), 마운트된 slot 재마운트 시 throw (`slot-plan.md`)
7. **컴포넌트 작성** — 컴포넌트 = 순수 함수, 리프 프로퍼티엔 State만 바인딩, 전역 store 직접 참조 금지(이식성) (`component-composition-plan.md`, `purity-and-effects-plan.md`)
8. **컴포넌트 경계 넘기기**`props.Modifier`/`props.Ref` named parameter 패턴 (`component-composition-plan.md`)
9. **이벤트** — self(Instance) 안 받음, 문자열 키(`Frame { MouseButton1Click = fn }`) (`bind-system-plan.md`)
10. **생명주기** — GC 위임(수동 정리 불필요), Destroy 이후 대상 재사용 금지 (`lifecycle-pattern.md`)
11. **Ref 기초** — 외부 관리 Instance 참조/마이그레이션용, `CreatedRef(fn, {phase=...})` (`architecture.md`, `bind-system-plan.md`)
12. **파생값 최소 예시**`:With(...)` + `:Compute(fn)` 기본형 (`bind-system-plan.md`, `store-semantics.md`)
13. **Tween 기초**`[Tween(key, ...)] = storeValue`, 취소 시 현재 보간값에서 자연스럽게 이어짐 (`research/tween-plan.md`)
14. **UI 숏핸드(quad-roblox 한정)**`Corner`/`PaddingAllOffset`/`Scale` 인라인 키 (`research/ui-shorthand-plan.md`)
---
## 2. 파일별 상세 분류
### architecture.md
- 초심자: DOMless 즉시 Instance 생성 모델 / 특수 바인드 키 / Ref 기본 개념 / modifier 기본 사용법(스타일링) / Store·State·Source 온톨로지 핵심 동작 / quad-base·quad-roblox 패키지 구조 존재 사실
- api: Class 함수형+`:` 체이닝 예외 규칙 / store 바인드=전체 교체 의미론 / Tag/retract(TagService 기반) / modifier 병합 우선순위 규칙(→심화: CSS cascade 회피 근거) / PropertyChangedSignal이 pluggable 핸들러로 구현 / Source·State·Store 타입 정의
- 심화: Class가 OOP 아닌 함수형인 이유 / metatable 체이닝 폐기 이유(v1 clone 문제) / id 기반 전역 조회 폐지 이유 / Style(Default) 시스템 폐기→modifier 대체 근거 / 멀티 백엔드(GTK 등) 지향 이유 / push-invalidate·pull-recompute 전파 모델 상세, 다이아몬드 의존성 해결 근거
- skip: Tracker 미구현, lang 모듈 분리, Signal 클래스 미구현 판단 과정, 소스 트리·모노레포 구조, 테스트 전략(mock 설계)
### comparison-fusion-vide.md — 대부분 skip(내부 리서치 스냅샷)
- **quadnomicon**으로 재작성 가치 있는 것 두 개(2026-08-06 재분류 — 원래
심화 후보였다가, 독자층이 "quad 사용자"가 아니라 "프레임워크 설계 자체에
관심 있는 엔지니어"라 quadnomicon으로 이동): **Slot 단일 마운트 소유권**이
Fusion/Vide 둘 다에 없는 quad만의 차별점(Fusion/Vide 경험자 대상 "왜 이중
mount를 막는가" 비교 소재) / **`:With`+`:Compute` 명시적 파생값**이 Vide의
암묵적 ambient stack 대신 채택된 이유(Vide 경험자 대상 비교 설명, 원문
재작성 필요) — 단, "왜 Slot은 단일 마운트를 강제하는가" 자체(다른 프레임워크
비교 없이 quad 논리만으로 설명 가능한 부분)는 여전히 심화에 남음(아래 4번
9번 항목).
- 나머지(Fusion 반응 그래프 BFS 분석, Scope 정리 모델, Vide 디스패치 분석, 비교표 전체)는 전부 내부 설계 근거 수집용, skip — `.claude/` 내부 설계사로만 남고 publish 대상 아님
### quad-v1-architecture.md — 전체 skip
v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부 회고. 단, **v1에서
넘어오는 기존 사용자용 마이그레이션 가이드**가 나중에 별도 문서로 계획된다면
그때만 재사용 가치 있음 — 지금 3축 어디에도 해당 없음.
### bind-system-plan.md (943줄, 최대 문서)
- 초심자: Source/Store/State 기본 정의+생성자, State 읽기 전용 규칙 / dot-access가 값 읽기 1급 경로 / `:With`+`:Compute` 최소 사용법 / Ref 기본 개념+`CreatedRef` / 이벤트 self 미채택 기본 규칙+문자열 키 / 인스턴스 생성(제네릭+정적 필드) / 라이브러리 초기화 3줄(`RobloxFactory(QuadBase)`)
- api: `state:Observer(fn)` 사용법(→심화: weak-table 내부 인덱싱) / `:Subscribe()`/`:Unsubscribe()` 시그니처(→심화: 강참조 레지스트리 구조) / Ref 일반화 표면 API(→심화: "왜 값이 아니라 콜백인가") / 이벤트 store-bind 존재+권장 안 함 가이드(→심화: 엔지니어링 비용 근거) / 핸들러 4종 계약(`isHandlable`/`priority`/`process`/`retract`) / `Attribute<T>` 특수 키 후보(미확정 명시 필요)
- 심화: push-invalidate/pull-recompute 전파 모델+"관측해야 실체화된다" 원칙+`previous` 캐비엇 / **왜 State를 Modifier처럼 플래튼하지 않는가**(이미 문서화 완료, 아래 3번 참고) / Store가 Store를 못 담는 이유 / 이벤트 self 미채택 4가지 근거 / store-bind 재귀 래핑 내부 메커니즘, retract가 Destroy 시 호출 안 되는 이유 / 같은 팩토리 재호출 no-op·다른 팩토리 충돌 에러 내부 안전장치
- skip: quad2-try 리서치 결과 섹션 전체(OOP 상속/커스텀 파서/Slot 스텁/`Pipe` 폐기 이력) / PA님 코드 교차검증 절(역사적 검증 기록) / "남은 열린 질문"/"확정된 것" 메타 요약
### component-composition-plan.md / module-lifecycle-plan.md
- 초심자: 컴포넌트=순수 함수 / 리프 프로퍼티엔 State만 바인딩 / `props.Modifier`/`props.Ref` named parameter 경계 전달 / `InitRoblox(Module)` 팩토리 초기화
- api: State(파생, 읽기전용) vs Source(원본, 쓰기가능) 경계 요약(→심화) / Slot 반환 컴포넌트는 Modifier/Ref 파라미터 미선언 / `Modifier.Merge(mod1, mod2, ...)` 유틸 / 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 네이밍 미정 등 열린 질문 메모
### lifecycle-pattern.md / purity-and-effects-plan.md
- 초심자: 수동 정리 불필요(GC 위임) / Destroy 이후 재사용 금지 / 컴포넌트는 파라미터로 받은 store만 사용(전역 store 직접 참조 금지)
- api: `Connected`/canExecute 인터페이스(→심화) / 생명 바인드 유틸 시그니처(→심화) / 이식성 규칙이 린트 강제가 아니라 컨벤션이라는 사실
- 심화: `Connected`가 계산된 속성인 이유(rbvm 근거) / `Instance.Destroying` 훅 단일화 이유 / weak-table GC-native 원칙+eager 정리 예외 / Signal 클래스 미채택 이유 / "quad는 생명주기 중간 계층이 아니다" 소유권 모델 / `retract` 네이밍 배경 / "순수함수 아니라 이식성 문제"로 재정의된 배경(vdom 없음 전제)
- skip: rbvm 조사 세션 메타, EventDrivenProgramming 교차검증 일화(결론만 심화에 남음)
### modifier-plan.md / slot-plan.md
- 초심자: Modifier 기본 체이닝+merge 우선순위 규칙 실제 예시 / Slot 기본 개념(children 배열)+클래스가 슬롯 받는 방법(Named Slot 없음) / 마운트된 slot 재마운트 시 즉시 throw
- api: Setter가 리터럴/변환 함수 둘 다 받음(→심화: getter 없는 이유) / 필드가 State일 수 있는 4가지 조합 표(→심화: 반응성 유지/끊김 이유) / `Modifier.Rounded(8)` dot-access 생성자 관습 / Slot은 인스턴스당 여럿 가능 / 중첩 인스턴스 자식 처리 / retract 시 slot 내용 폐기(→심화: portal 없는 이유)
- 심화: 정적 merge vs 런타임 pluggable 기각 이유(CSS cascade) / immutable+clone 체이닝 이유(형제 오염 방지) / getter 미채택 이유 / `__index` 런타임 구현 통찰 / Modifier가 핸들러 계층을 모르는 이유 / base/roblox 패키지 경계(Dispatch/Slot vs Handlers/Slot) / Slot 단일 마운트 소유권이 v1/Fusion/Vide 대비 개선인 이유 / retract=폐기 확정 히스토리(portal 검토 후 기각)
- 열린 질문(문서화 보류): 여러 Slot이 형제로 섞일 때 순서 보장 — 미확정
- skip: 세션 날짜/확정 이력, 문서 승격/정정 안내
### store-semantics.md / tween-plan.md / ui-shorthand-plan.md
- 초심자: Store 생성+`myStore.key = value` 문법 / `store.key`로 State 얻기 개념 / Tween 기본 바인드 키+취소 기본 동작 / UI 숏핸드 인라인 키 기본 예시(`Frame { PaddingAllOffset = 50 }`)
- api: `:With`+`:Compute` 시그니처(→심화) / `source:Emit()` 존재+"Get() 결과 캐시 금지" 캐비엇(버그 유발 포인트라 api에도 명시 가치 있음, →심화; 2026-08-06 후속 세션에서 `Store:Emit(key)`→`source:Emit()`로 호출부 변경, `store-semantics.md` 참고) / Tween 핸들러가 Instance 직접 받음(Ref 불필요) / retract는 Destroy 시 호출 안 됨(→심화) / UI 숏핸드 키 목록 레퍼런스 표 / Modifier와 순수 인라인 키 동등성
- 심화: Source·Store·State·Observer 온톨로지(독립 프리미티브 vs 파생 데이터 원칙, 생성자 모양 근거) / `Emit`이 Source 전용인 이유(디버깅 그래프 무결성) / `Store<T>`의 T가 Modifier 불가인 이유 / Tween을 반응 그래프 밖 특수 bind key로 둔 이유(Fusion 반면교사) / RoundSize 포팅 불필요 vs Corner/PaddingAll/Scale 필요 이유 / "작고 opt-in 아닌 편의 기능은 코어 포함" 원칙
- 열린 질문(문서화 보류): tween-plan.md의 오버라이드/삭제후재시작/끝점이동 옵션 키 이름 미정 / ui-shorthand의 RoundSize 완전 드롭 여부
- skip: 세션 정정 이력, v1 소스 조사 경위
---
## 3. 이미 작성 완료된 심화 콘텐츠
- **왜 State 체인을 Modifier처럼 플래튼하지 않는가**`base/bind-system-plan.md` 해당 절에 결정문 있음(2026-08-06 세션에서 이 대화 중 확정).
## 4. 심화 전용 신설 콘텐츠 후보 (반복 테마 정리, 에세이 단위)
위 표에서 반복 등장하는 "왜" 주제들을 에세이 단위로 묶으면:
1. 왜 함수형 컴포넌트인가(OOP 상속 대신) — `architecture.md`, `component-composition-plan.md`
2. 왜 Modifier는 런타임 pluggable이 아니라 정적 flatten인가 — `modifier-plan.md`
3. 왜 push-invalidate/pull-recompute인가(Fusion eager 노드 미채택) — `bind-system-plan.md`
4. 왜 State는 플래튼하지 않는가 — 작성 완료(위 3번)
5. 왜 GC-native 생명주기인가(Signal 클래스 없음) — `lifecycle-pattern.md`
6. 왜 이벤트 핸들러는 self를 안 받는가 — `bind-system-plan.md`, `research/documentation-plan.md` 3번과 통합 가능
7. 왜 컴포넌트 경계는 named parameter인가(Compose/Fusion/Vide/v1 수렴) — `component-composition-plan.md`
8. 왜 "다중 루트 반환" 개념을 없앴는가 — `component-composition-plan.md`
9. 왜 Slot은 단일 마운트 소유권을 강제하는가(v1/Fusion/Vide 대비) — `slot-plan.md`, `comparison-fusion-vide.md`
10. 왜 Tween은 반응 그래프 밖에 있는가 — `research/tween-plan.md`
11. 왜 `:Emit()`은 Source 전용이고 파생 State엔 없는가(호출부는 `source:Emit()`, 2026-08-06 후속 세션에서 `Store:Emit(key)`→이 형태로 정리) — `store-semantics.md`
12. 독립 프리미티브 vs 파생 데이터 — 생성자 모양을 결정하는 원칙 — `store-semantics.md`
14. 왜 컴포넌트는 전역 store를 직접 참조하면 안 되는가(이식성) — `purity-and-effects-plan.md`
15. 왜 Source가 State를 구조적으로 만족하는가(Svelte Writable/Readable과 같은 서브타입 모양, `RefSource`/`StoreSource` 중간안이 왜 기각됐는가) — `store-semantics.md`, `component-composition-plan.md`
(13번이었던 "Fusion/Vide 경험자용 비교 섹션"은 2026-08-06 재분류로 아래 6번
`quadnomicon`으로 이동)
## 6. `quadnomicon` — 4번째 축, 프레임워크 설계자용 (2026-08-06 신설)
**독자층이 다름**: 심화(1~5번)는 "quad를 깊게 이해해 최적화하거나 왜
이런지 이해하고 싶은 quad 사용자"용. `quadnomicon`은 "비슷한 반응형 UI
프레임워크를 직접 설계/포크하려는 엔지니어"용 — quad를 그냥 쓰기만
한다면 평생 안 읽어도 무방한 콘텐츠. Rustonomicon 패러디로 이름 확정
(사용자 선택).
**현재 후보(둘 다 `comparison-fusion-vide.md`에서 재작성 필요, 원문
그대로 쓰면 안 됨 — 지금은 우리 내부 리서치 원자료 톤)**:
1. Slot 단일 마운트 소유권이 Fusion/Vide 둘 다에 없는 quad만의 차별점 —
"왜 이중 mount를 막는가"를 Fusion/Vide 내부 동작과 나란히 비교
2. `:With`+`:Compute` 명시적 파생값이 Vide의 암묵적 ambient stack 대신
채택된 이유 — Vide 경험자 대상 비교
**publish 안 하는 것과의 경계**: 세션별 정정 이력, 조사 원자료(Fusion
반응 그래프 BFS 분석, quad2-try 죽은 코드 조사 등)는 quadnomicon에도
안 들어감 — 그건 새 티어가 필요한 게 아니라 애초에 `.claude/` 내부
설계사로만 남고 절대 publish 안 하는 것(RFC 논의 저장소 같은 성격,
위 각 파일 섹션의 skip 참고). quadnomicon은 잘 다듬은 소수의 큐레이션된
에세이 공간이지, 내부 연구 기록을 그대로 옮기는 곳이 아님.
**배경지식 자체가 깊은 주제(예: GC) 처리 방침**: 새 티어를 만들지 않음.
"quad가 GC를 어떻게 활용하는가"(quad 고유)는 심화에 그대로 두되, "GC란
무엇인가" 자체를 가르치는 자체 튜토리얼은 안 쓰고 외부 좋은 자료로
링크 처리 — 안 그러면 문서 프로젝트가 일반 프로그래밍 교육 쪽으로
스코프 크리프될 위험이 있음(사용자 판단).
## 5. 문서화 아직 보류(미확정 설계라 쓰면 안 됨)
- Slot 형제 순서 보장 (`slot-plan.md`)
- Tween 오버라이드/삭제후재시작/끝점이동 세부 옵션 키 이름 (`research/tween-plan.md`)
- UI 숏핸드 `RoundSize` 드롭 여부 (`research/ui-shorthand-plan.md`)
- `Attribute<T>` 제네릭 vs 타입별 정적 생성자 (`bind-system-plan.md`)
- provider/processor 네이밍 (`module-lifecycle-plan.md`)
이 항목들은 `.claude/question.md`에도 이미 열린 질문으로 잡혀있음 — 여기선
"확정 전엔 문서화 대상 아님"이라는 표시만 겸함.
## 다음 단계
이 맵 자체를 지금 실행할 필요는 없음(구현 착수가 여전히 최우선,
`documentation-plan.md` "다음 단계" 참고). 나중에 실제로 문서 사이트
작업을 시작할 때: (1) 위 1번 목차 초안으로 초심자 트랙 스캐폴딩, (2) 파일별
[api] 항목으로 레퍼런스 페이지 스캐폴딩, (3) 4번 리스트를 심화 섹션
에세이 백로그로 사용.

View file

@ -5,7 +5,80 @@
판단해 뼈대만 기록해둠. 세부 설계는 없음, 착수 시점도 미정 — 사용자가
"간단히 plan 작성은 하는 게 맞을 듯, 아주 뼈대만 있는 것이라 할지라도
적어줄 필요는 있겠다"고 판단해서 만든 문서. 같은 날 후속 세션에서
이벤트 핸들링 self 관습 논의가 확정되며 3번 항목이 추가됨.
이벤트 핸들링 self 관습 논의가 확정되며 3번 항목이 추가됨. 별도 세션
(날짜 미상, 다음 세션이 채워 넣을 것)에서 전체 문서 사이트 구조 논의가
추가돼 0번 항목으로 들어감.
## 0. 전체 문서 사이트 구조 — 3축 + 백엔드별 트랙 분리 (확정, 착수는 여전히 후순위)
**축 구조 확정**: 초심자(getting started, 전반적 패턴을 실행 가능한
예제로) / api(레퍼런스, 노출 표면 전부를 빠르게 훑어보기 용) / 심화(왜
quad가 그렇게 설계됐는지 기술적 근거, 최적화·대규모 코드베이스 관리
관심자용) 3축으로 관리. Diátaxis 프레임워크(tutorial/reference/
explanation)와 사실상 동형 — 검증된 패턴이라 그대로 채택. 원래 4분면인
Diátaxis의 "how-to guides"(태스크 지향 레시피)에 해당하는 콘텐츠는
별도 축을 새로 만들지 않고 초심자 트랙 안에 녹이기로 함(아래 "초심자
스코프" 참고, 2번 항목의 "Store 부작용 패턴" 문서가 대표 사례).
**초심자 문서 스코프 — "core loop 완주에 필요한 최소 집합만"**:
프리미티브(Source/State/Modifier 등)와 그 주요 파생 데이터 입력값
정도는 처음부터 가르치기 쉬움(quad엔 가상 DOM이 없어서 개념 자체가
단순함). 반면 `Compute``previous` 인자, Observer, Store `:Emit`,
이벤트 store-bind, Slot 형제 순서 같은 건 "필요해지면 찾아보는" 것으로
api/심화에 위임 — 처음부터 노출하면 core loop이 흐려짐. 로블록스
스크립팅 유저 간 실력 편차가 큰 문제는, 초심자 트랙 자체를 짧고 선형으로
유지하면서 각 개념 옆에 api/심화로 새는 링크를 촘촘히 박아두는 방식으로
대응 — "쉬운 버전/어려운 버전"을 따로 쓰지 않고 한 문서 안에 탈출구를
많이 두는 쪽.
**초심자 문서에 quad-base/quad-roblox 분리를 노출하지 않기로 확정**:
지금은 백엔드가 quad-roblox 하나뿐이라, 초심자에게 "이건 base 프리미티브,
이건 roblox 전용"이라고 구분해서 가르치면 실행도 안 해본 추상 개념부터
마주치게 됨 — 대신 `Source`/`State`/`Modifier` 등을 전부 구체적인
Roblox 코드(Frame 만들기 등) 안에서 자연스럽게 등장시키고, 엔진 무관하다는
사실은 각주 정도로만 언급. 이 구분이 실제로 값어치를 하는 곳은 (a) api
문서 — 패키지별로 quad-base 페이지/quad-roblox 페이지를 나눠 "이 함수가
어디서 왔는지" 룩업 가능하게, (b) 심화 문서 — base/roblox를 왜 쪼갰는지
설계 근거 자체가 콘텐츠.
**백엔드별 튜토리얼은 필요해질 때 추가 — 지금 설계 불필요**: 초심자
문서는 메이저 버전업이나 개명 수준이 아니면 거의 안 바뀌는 성격이라,
나중에 quad-web/love2d 등 실제로 다른 백엔드가 분리되어 나올 때 그
사용자들을 위한 튜토리얼을 그때 가서 추가하면 됨 — 이벤트 핸들링 모델도
다르고 Tween처럼 아예 없는 백엔드도 있을 수 있어 지금 추상 개념을
먼저 가르치려 하면 없는 공통분모를 억지로 만드는 셈이라 오히려 나쁨.
즉 초심자 트랙은 **백엔드별로 완전히 분리된 트랙**(지금은 quad-roblox
하나)으로 구조화하고, "quad 코어가 뭔지"는 심화에서만 다룸.
**착수 시점**: 위 1~3번 항목과 동일하게 지금 당장 설계/작성할 필요는
없음(`CLAUDE.md` "지금 할 일" 1번 — 구현 착수가 최우선). 이 0번 항목은
"만약 시작한다면 이런 구조로"에 대한 합의이지, 착수 신호는 아님.
**콘텐츠 분류 완료(2026-08-06)**: 위 3축을 실제로 뭘로 채울지
`.claude/base/*.md` 전체 + 관련 `research/*.md`(tween-plan, ui-shorthand-plan)를
초심자/api/심화/skip으로 서베이한 결과는 `research/documentation-content-map.md`
참고 — 초심자 core loop 목차 초안, 파일별 상세 분류, 심화 전용 신설 콘텐츠
후보(14개 에세이 소재), 아직 미확정이라 문서화 보류해야 할 항목까지 정리됨.
api 항목은 간략 설명 + 심화로 "더 알아보기" 링크를 다는 패턴(사용자 확정)도
그 문서에 반영.
**4번째 축 신설 — `quadnomicon`(2026-08-06, 같은 세션 후속)**: 콘텐츠 분류
중 "심화"로 분류하기엔 성격이 다른 두 갈래가 섞여 있는 게 드러남 —
(a) Fusion/Vide 내부 구현 비교, 다른 프레임워크 선례 조사처럼 "quad를 깊게
이해해 최적화하려는 사용자"가 아니라 "비슷한 프레임워크를 직접 설계/포크
하려는 엔지니어"를 위한 콘텐츠, (b) 세션별 정정 이력/조사 원자료처럼 애초에
공개 문서화 대상이 아닌 내부 설계사. **(b)는 새 티어도 필요 없이 그냥
`.claude/` 내부 설계 기록으로 남고 절대 publish 안 함**(Rust로 치면
rust-lang/rfcs 저장소처럼 raw 상태로만 존재). **(a) 중 잘 다듬은 소수만**
Rustonomicon 패러디 이름 `quadnomicon`으로 새 4번째 축에 publish —
독자층은 quad 사용자가 아니라 프레임워크 설계 자체에 관심 있는 엔지니어.
이름은 사용자가 여러 후보(quadnomicon/심해/해구/흑마법서) 중 직접 확정.
**GC처럼 quad 고유 내용은 아니지만 배경지식 자체가 깊은 주제 처리 방침**:
새 티어를 안 만들고, "quad가 GC를 어떻게 활용하는가"(quad 고유)는 심화에
그대로 남기되 "GC란 무엇인가" 자체는 자체 튜토리얼을 쓰지 않고 외부 좋은
자료로 링크 처리하는 쪽으로 방침 확정 — 안 그러면 문서 프로젝트가 일반
프로그래밍 교육 쪽으로 스코프 크리프될 위험이 있다는 판단.
## 1. UI 요소 네이밍 컨벤션 문서
@ -85,6 +158,19 @@ UICorner/UIPadding/UIScale 숏핸드가 만드는 자식)는 `_`나 `QUAD_` 같
- 어느 문서에 넣을지 — 퀵스타트? 위 1번 네이밍 컨벤션 문서와 마찬가지로
아직 미정.
## 4. 심화 문서 소재 후보 (누적, 2026-08-06 추가)
설계 논의 중 "이건 왜 이렇게 안 했는가/이렇게 했는가" 식으로 나온 세세한
근거들 중, 나중에 0번 항목의 "심화" 축에 넣을 만한 것들을 누적 기록.
지금 당장 문서를 쓰라는 뜻 아님 — 원 결정문은 각 `base/` 문서에 이미
있으므로 여기선 소재 제목과 위치만 남김:
- **왜 State 체인을 Modifier처럼 플래튼하지 않는가** — 결정문은
`base/bind-system-plan.md` "왜 State 체인을 Modifier처럼 플래튼하지
않는가" 절. State의 정의가 "캐싱하는 존재"라 다이아몬드 파생에서
캐시 공유를 잃으면 `previous` 메커니즘이 막으려던 중복 계산 문제가
재발한다는 게 핵심 논거.
## 다음 단계
셋 다 지금 당장 설계할 필요는 없음(`CLAUDE.md` "지금 할 일" 1번 —

View file

@ -0,0 +1,110 @@
# quad vs Fusion/Vide/react-lua — 정직한 비교 (2026-08-06)
**상태**: research — 3개 에이전트가 각각 Fusion(`.claude/initreq/fusion` 실
소스), Vide(`.claude/initreq/vide` 실 소스), react-lua(로컬 클론 없어
웹 리서치)를 직접 읽고 quad의 확정 설계와 대조. 목적은 마케팅이 아니라
정직한 자가점검 — "quad가 진짜 나은 부분"(나중에 초심자/quadnomicon 문서의
"왜 quad인가" 소재)과 "quad가 진짜 불리한 부분, 그중 고칠 수 있는 것"을
사용자가 직접 검토하기 위함. **quad는 구현 0줄** 상태라 모든 비교가
"검증된 프로덕션 코드 vs 종이 설계"라는 근본적 비대칭을 안고 있음 — 아래
모든 강점/약점은 이 전제하에 읽을 것.
## 1. quad가 실제로 나은 점 (소스 근거 있음, 향후 "왜 quad인가" 문서 소재)
- **Slot 단일 마운트 가드 — Fusion·Vide 둘 다 없음, 실재하는 버그 클래스를 막음.**
Fusion `Children.luau``-- TODO: check for ancestry conflicts here`
주석이 그대로 남아있고 이미 마운트된 인스턴스를 조건 없이 재부모화함(조용한
이중 마운트). Vide `mount.luau`도 중복 마운트 체크가 전혀 없음. quad의
"이미 마운트된 Slot 재마운트 시 즉시 throw"는 둘 다에 없는 실질적 안전장치.
- **열린 우선순위 축 — Fusion의 하드코딩된 4단계보다 확장성 좋음.** Fusion
`applyInstanceProps.luau``{self, descendants, ancestor, observer}`
정확히 4개 버킷만 갖고 5번째를 쓰면 에러남. quad의 열린 숫자 우선순위
레지스트리는 커스텀 bind key를 라이브러리 수정 없이 임의 우선순위에
끼워넣을 수 있음.
- **명시적 의존성이 여러 버그 클래스를 원천 차단.** Vide는 전역 `scopes`
스택 기반 암묵 추적이라 리액티브 스코프 안 yield가 그래프를 깨는 걸 막기
위해 별도 `ycall` 장치까지 둠(`graph.luau`). quad의 명시적 `:With`
의존성 전달은 이 버그 클래스 자체가 발생하지 않음.
- **다이아몬드 의존성 재계산 dedup — Vide가 스스로 미해결로 남긴 문제를
더 구조적으로 해결.** Vide `todo.md`가 diamond 그래프 중복 재평가 방지를
미해결로 인정했고, 실제로 `test/tests.luau`의 "recursive queue flush
diamond" 테스트가 이상적 2회 대신 3회 실행됨을 재현함. quad의 `invalid`
플래그 dedup은 이걸 원시 레벨에서 막도록 설계됨.
- **fine-grained라 vdom 특유의 버그 클래스가 통째로 없음(vs react-lua).**
react-lua는 리스트 diffing을 위해 key 관리가 필요하고(불안정하면 자식
상태 유실), hooks 호출 순서 규칙이 있으며(위반 시 "Rendered fewer/more
hooks" 에러), 고빈도 갱신엔 리렌더를 우회하는 별도 API(`Bindings`)를
공식적으로 추가해야 했음(react-lua 스스로 "vdom 재조정만으론 부족하다"고
인정한 셈). quad는 모든 값이 동일한 push-invalidate/pull-recompute
모델이라 이 세 문제 자체가 없음.
- **Tween을 그래프 밖에 둬서 구조적 복잡도를 회피.** Fusion `Animation/
Tween.luau`는 `Stopwatch`+`ExternalTime` 그래프 노드와
`checkLifetime.bOutlivesA` 교차 lifetime 검증까지 필요한 3중 장치.
quad엔 이 장치 자체가 없음(단, 반대급부는 아래 3번 참고).
## 2. quad가 불리한 점 중 — 고칠 만한 것(fixable, 검토 가치 있음)
- **use-after-destroy 검증 안전망 부재.** Fusion `Memory/checkLifetime.luau`
"짧게 사는 스코프가 오래 사는 대상에 바인딩됐다" 같은 실수를 사람이 읽을
수 있는 에러 메시지로 즉시 잡아줌. quad `base/lifecycle-pattern.md`
이런 사전 검증 개념이 없음. GC-native 프로덕션 동작 자체를 바꿀 필요는
없고, **개발/Studio 모드 한정 옵트인 검증 레이어**(quad-debug류와 결합
가능)로 추가하는 정도는 GC-native 철학과 안 부딪히고 고려해볼 만함.
- **`:With(...)` 정적 의존성 목록 — Fusion의 동적 재평가보다 약함.** Fusion
`evaluate.luau`는 매 평가마다 실제 `use()`된 의존성만 다시 구독해 특정
라운드엔 조건부로 일부 의존성을 아예 구독 안 할 수 있음. quad는 `:With`
에 나열한 목록이 Compute 시점에 고정돼, lazy handle로 재계산 트리거는
피해도 무효화 신호 자체는 계속 도착해 불필요한 재-Get이 누적될 수 있음.
**동적 With 등록/해제 API** 정도로 완화 가능해 보임 — 우연한 갭에 가까움.
- ~~Store dot-access가 매 접근마다 새 State를 할당~~**[해소됨,
2026-08-06 세 번째 세션]** 이 항목이 직접 트리거가 되어 Source/State
관계 자체를 재구성(`store-semantics.md` "Source가 State를 만족함" 절) —
Store가 이제 생성 시 만들어둔 Source를 그대로 반환해 wrapper 할당 자체가
없어짐, 구현 단계 최적화가 아니라 설계로 완전히 없앰(캐싱/풀링보다도 쌈).
## 3. quad가 불리한 점 중 — 못 고치는 것(의도된 트레이드오프, "고친다" 개념 자체가 안 맞음)
- **암묵적 추적의 인체공학적 우위(vs Vide)**`derive()` 안에서 그냥
호출하면 의존성이 잡히는 Vide 대비, quad는 전부 `:With`에 나열해야 해
보일러플레이트가 늘어남. quad가 "Lua에서 암묵 추적은 부작용 관찰이
필요해 지저분하다"는 이유로 의도적으로 거부한 결과라, 명시성을 유지하는
한 고칠 개념 자체가 아님(경감책은 있을 수 있음 — 아래 4번 참고).
- **Tween이 그래프 밖이라 다른 Compute의 입력으로 자유롭게 합성 불가(vs
Fusion/Vide)** — Fusion Tween/Spring, Vide `spring()`은 그래프 노드라
다른 파생값의 입력으로 얽어 쓸 수 있음. quad는 Fusion을 반면교사 삼아
의도적으로 이 경로를 포기한 것이라 원 설계 취지와 충돌. 필요해지면
옵트인 브릿지 추가가 현실적 타협(지금 급한 건 아님).
- **GC-native 라이프사이클 자체가 안고 있는 리스크** — Vide는 GC와
`Instance.Destroying` 순서가 비결정적이라는 알려진 함정 때문에 의도적으로
eager·수동 cleanup을 택함. quad의 "수동 dispose 불필요"는 GC 의존을
없애려면 결국 Vide식 수동 owner 트리로 돌아가야 해서 철학과 충돌 —
다만 `base/lifecycle-pattern.md`의 rbvm 실물 검증 근거로 리스크는 이미
어느 정도 완화돼 있음(기존 base 문서 참고).
- **DOMless+컴포넌트 1회 실행 때문에 "지금 트리가 어떻게 생겼는가"를
한눈에 재구성하기 어려움(vs react-lua)** — react-lua는 렌더마다 전체
서브트리를 선언적으로 다시 기술해 현재 상태가 코드 한 곳에 드러남. quad는
변화가 개별 leaf bind에 흩어져 처리돼 복잡한 조건부 트리 추론이 어려움.
근본 선택에서 필연적으로 따라오는 트레이드오프라 설계 변경으론 해소 안
되고, quad-debug 같은 관측 도구로만 보완 가능(이미 백로그에 있음 —
`research/debug-tooling-plan.md`).
## 4. 성숙도 격차 — 설계 결함 아니지만 지금 시점 비교에선 정직하게 명시해야 함
Fusion(~5000줄+테스트+수년 실사용), Vide(2800줄+테스트+0.1.0→0.4.1 하드닝
이력), react-lua(Roblox 사내 실사용+전용 벤치마크 레포)는 전부 실전에서
발견되고 고쳐진 문제들의 산물. quad는 구현이 0줄이라 이 비교의 강점 항목도
전부 M0 스파이크 이후 실제 Luau로 검증돼야 신뢰할 수 있고, 구현이 진행되면
유사한 이유로 비슷한 안전장치를 뒤늦게 추가하게 될 가능성이 있음(1번의
use-after-destroy 검증처럼). "hooks 없는 quad의 `:With`/`:Compute`가
React 커스텀 훅만큼의 합성성을 실사용 규모에서 주는가"도 지금은 데이터
없음 — 고칠 문제인지조차 판단 이를 정도로 이름.
## 다음 단계
이 문서 자체는 지금 당장 뭘 바꾸라는 결정문이 아님 — 사용자가 직접 검토
후 판단할 항목:
- 2번의 남은 두 가지(use-after-destroy 검증, 동적 With)를 실제로 설계에
반영할지, 반영한다면 언제(M0 스파이크 때 같이 검증할지, 나중 최적화
패스로 미룰지). 세 번째(Store dot-access 할당)는 위에서 이미 해소됨.
- 1번 강점 목록은 `research/documentation-content-map.md`의 "왜 quad를
쓰는가" 초심자/quadnomicon 콘텐츠 소재로 재사용 가능.

View file

@ -0,0 +1,567 @@
# 구현 착수 직전 감사 — 모호성 / 지연결정 리스크 / 단순화 후보
**상태**: research — 사용자 상의 필요. 2026-08-06 세션에서 신설. `.claude/base/`
전체가 "확정"으로 표시돼 있지만, 실제 `ROADMAP.md` M0 착수를 앞두고 구현자
시점에서 다시 크리티컬하게 훑어본 결과. 방법론: `base/` + 근접
`research/`(tween-plan, ui-shorthand-plan, existing-instance-bind-plan)를
4개 클러스터로 나눠 서브에이전트로 병렬 정독시키고, 각각 세 가지 렌즈로
리뷰하게 했음 —
1. **모호성**: 실 구현 중 "이 경우엔 정확히 어떻게 동작하지?"라고 멈춰서
다시 물어봐야 할 만한, 구체적 동작이 명시 안 된 지점.
2. **지연 결정 리스크**: 지금 "나중에 정해도 된다"고 취급되지만 사실 코어
타입 구조/패키지 경계/데이터 모델에 깊이 얽혀 있어 나중에 바꾸면 연쇄
파급이 클 것 같은 항목.
3. **오버엔지니어링/단순화 후보**: 확정됐다고 적혀 있지만 목적에 비해 과한
추상화로 보이거나 더 간단한 대안이 있어 보이는 지점. (단, "이미 여러
라운드에 걸쳐 검증됨"이라고 문서가 스스로 못박은 결정 자체를 재론하는
건 배제 — `CLAUDE.md`의 반복 조사 금지 원칙과 같은 이유.)
이미 `.claude/question.md`에 취합된 항목(용어 재검토, M0 스파이크 항목
자체, Slot 형제 순서 보장 등)은 여기서 제외했다 — 아래는 **전부 새로 발견된
것**. 부수적으로 이 감사 과정에서 `architecture.md`의 stale한 부분 두 곳
(온톨로지 요약 절, 소스트리 `Store.luau`/`Ref.luau` 주석)을 발견해 같은
세션에서 바로 고쳤다 — 판단이 필요 없는 순수 문서 동기화라 여기 남기지
않고 해당 문서에서 직접 정정함.
## 어떻게 쓸 것
우선순위 1은 M0~M4 구현 도중 실제로 부딪힐 가능성이 높은 것 — **가능하면
M0 착수 전에 확인**. 우선순위 2는 지금 결정해두면 싼데 안 해두면 나중에
비쌀 것들 — 해당 마일스톤 착수 직전에만 확인해도 됨. 우선순위 3은 사용자
판단에 달린 단순화 제안. 문서 모순 절은 이미 고친 것과 아직 안 고친 것을
구분해뒀다.
---
## 우선순위 1 — M0~M4 착수 전 확인 권장
### 1-1. Tween이 "범용 store-bind 캐치올 핸들러"의 유일한 예시로 쓰여, 일반 반응형 프로퍼티 바인딩과 혼동될 위험
**위치**: `base/bind-system-plan.md` "확정된 디스패치 모델" 절 67-79행 —
"Tween의 store-bind 핸들러는 **`k`는 무엇이든 받고 `v`가 Store인 경우를
잡아내는, 우선순위가 매우 높은 핸들러**"; `architecture.md` 소스트리엔 이
역할을 하는 quad-roblox 파일이 `Handlers/Tween.luau` 하나뿐(별도 범용
StoreBind 핸들러 파일 없음); `ROADMAP.md` M11도 Tween을 "높은 우선순위
store-bind 핸들러"로 서술.
**문제**: 이 문서 전체에서 "v가 store인 값을 구독해 realv로 재귀 process
하는" 범용 메커니즘의 **유일한 구체 예시가 항상 "Tween"으로만 등장**한다.
그런데 Tween(실제 애니메이션, override/cancel 정책)은 `research/
tween-plan.md`라는 별개 리서치 문서와 별도 로드맵 마일스톤(M11)을 가진,
명백히 더 좁고 아직 미확정인 기능이다. `Frame { BackgroundColor3 =
store.color }`처럼 애니메이션 없이 그냥 반응형으로 값만 바뀌길 원하는
가장 흔한 케이스가 (a) 결국 이름은 "Tween"인 파일을 거쳐가며 "애니메이션
없음"으로 처리되는 건지, (b) Property/Tag/Attribute 등 각 핸들러가 각자
`Dispatch/StoreBind.luau`(quad-base, 범용) 유틸을 직접 써서 독립적으로
구현해야 하는 건지 문서가 정하지 않았다. `ROADMAP.md` M4("첫 end-to-end
반응형 업데이트")는 Tween 없이(M11보다 훨씬 전에) `Dispatch/StoreBind.luau`
만으로 "store 값 바꾸면 process가 다시 호출된다"를 검증하게 돼 있어 (a)는
아닌 것 같지만, 그럼 M11에서 Tween.luau가 실제로 추가될 때 그게 기존
경로와 **레이어링(우선순위로 얹힘)되는지 대체되는지**가 불명확하다.
**제안**: "일반 store-bind(애니메이션 없음)"와 "Tween 전용 store-bind
(애니메이션 있음)"가 같은 핸들러인지 별개 핸들러인지부터 확정. 별개라면
소스트리에 `Handlers/StoreBind.luau`(또는 유사) 항목을 명시적으로 추가하고,
`Tween.luau`는 그 위에 얹히는 "값에 tween 설정이 붙어있으면 가로채는" 더
높은 우선순위의 특수 케이스로 재정리하는 게 자연스러워 보임.
### 1-2. retract 시 "이전에 실제로 매치됐던 핸들러"를 누가 추적하는지 불명
**위치**: `base/bind-system-plan.md` "확정된 디스패치 모델" 절 90-91행 —
"store bind가 새 값으로 넘어갈 때 이전 핸들러의 `retract(inst, k, v)`
한 번 호출해주면 됨."
**문제**: store-bind 재실행 모델에서 `realv`의 실질 타입은 매 갱신마다
바뀔 수 있다(예: 처음엔 숫자값이라 Property 핸들러가 매치, 다음번엔 다른
타입값이라 다른 핸들러가 매치). 이 경우 "이전 핸들러"가 정확히 어느
핸들러였는지는 `(inst, k)`별로 어딘가 기록돼 있어야 정확한 `retract`
대상을 찾을 수 있다. "핸들러 내부 상태 저장" 절은 "각 핸들러가 자기가
만든 것"(예: 실행 중인 Tween 객체)을 저장하는 패턴만 다루지, "이 키를
마지막으로 어느 핸들러가 담당했는가"라는 상위 레벨 라우팅 상태를 누가
(base 엔진 vs `Dispatch/StoreBind.luau` 래퍼) 관리하는지는 명시가 없다.
**제안**: `Dispatch/StoreBind.luau`가 "마지막으로 선택된 핸들러" 자체를
`(inst, k)`별 상태로 들고 있다가, 새 `realv` 처리 전에 그 핸들러의
`retract`를 호출하는 식으로 지금 결정해두는 게 좋아 보임 — M2/M4에서 바로
부딪힐 지점.
### 1-3. 우선순위 스캔의 동률 처리, 매치 실패 시 동작이 정의 안 됨
**위치**: `base/bind-system-plan.md` "핸들러 계약" 절 — "디스패치는 등록된
핸들러를 우선순위 순으로 스캔하며 `isHandlable`을 호출, 첫 매치가 처리."
**문제**: (a) 두 핸들러가 같은 `priority` 값을 가질 때 어느 쪽이 우선인지
(등록 순서? 정의 안 됨) 규칙이 없음. (b) 어떤 핸들러도 `isHandlable(k, v)`
만족하지 않는 `(k, v)` 쌍이 들어왔을 때 — 조용히 무시? 에러? — 도 정의가
없음. 후자는 특히 사용자가 오타 키를 쓰거나 지원 안 되는 조합을 넣었을 때
디버깅 경험에 직결.
**제안**: 최소한 "매치 실패는 에러(silent 무시 금지)"만이라도 지금
결정해두면 구현 중 인터럽트를 막을 수 있음. 동률은 "등록 순서가 tiebreak"
정도로 명시만 해둬도 충분. M2(Dispatch 엔진) 착수 직전 확인.
### 1-4. provider(팩토리) 미주입 상태에서 dispatch가 호출되면 어떻게 되는지 세 번째 케이스가 빠짐
**위치**: `base/module-lifecycle-plan.md` "Bind는 누가, 어떻게 구현하는가"
절 — 재호출 가드는 "같은 팩토리=무시, 다른 팩토리=에러" 두 케이스로
확정됐지만, 이건 전부 "팩토리가 이미 한 번 실행된 이후" 얘기다.
**문제**: 원문이 처음 언급했던 세 번째 케이스 — **아직 아무 팩토리도 실행
안 된 상태에서 dispatch(`process`/`retract`)가 호출되는 경우**(예:
`InitRoblox` 호출 전에 컴포넌트를 마운트 시도)는 이후 어느 문서에서도 다시
다뤄지지 않았다. 이때 정확히 뭐가 일어나는지(명시적 에러 메시지 vs
nil-index 크래시 vs 조용한 no-op)가 안 정해져 있음.
**제안**: base dispatch 엔진이 "아직 provider 미주입" 상태를 감지해 명확한
에러를 던지도록 지금 결정해두면, 구현 중 흔한 초기화 순서 실수를 훨씬 덜
헷갈리게 만들 수 있음. 1-2번과 같은 타이밍(M2)에 같이 확정.
### 1-5. `props.Modifier`/`props.Ref` forwarding 관례가 Lua 배열 리터럴의 nil-hole 함정에 그대로 노출됨
**위치**: `base/component-composition-plan.md` "최종 결론" 1번 —
`return Frame { props.Modifier, props.Ref, ... }` 패턴.
**문제**: caller가 `props.Modifier``props.Ref`를 안 넘기면 그 값은
`nil`이다. Lua 테이블 생성자에서 `{nil, refValue, child}``t[1]`
사실상 키 없는 상태가 되지만 `t[2]`, `t[3]`은 정상적으로 채워진다. 이때
디스패치 루프가 v1처럼 `ipairs`로 배열을 순회한다면 **`t[1]`이 nil이라는
이유만으로 `t[2]`(Ref)와 `t[3]`(자식)까지 통째로 무시**될 수 있다 — Ref
콜백이 조용히 안 불리고 자식도 안 그려지는, 원인 추적이 매우 어려운 버그
클래스. 이 문서가 정식으로 권장하는 forwarding 패턴 자체가 이 함정을
유발하는 전형적 모양인데 nil 처리 규칙이 전혀 언급되지 않는다.
**제안**: (a) 디스패치 루프를 `ipairs` 대신 `#t` 기반 명시적 인덱스 루프나
`table.pack`/센티널로 nil-safe하게 만들거나, (b) forwarding 관례 자체를
`Frame { Modifier = props.Modifier, Ref = props.Ref, [1] = child }`처럼
명시적 키로 넘기게 하거나, (c) 최소한 "props.Modifier/Ref가 nil일 수
있으니 배열 위치에 직접 넣지 말라"는 경고를 문서에 남길 것. **M0
스파이크가 이 패턴을 이미 검증 대상으로 잡고 있으니(`props.Modifier`/
`props.Ref` named-parameter 컴포넌트 작성), 그 스파이크 코드에 caller가
Modifier/Ref를 아예 안 넘기는 케이스를 반드시 포함시킬 것.**
### 1-6. `canExecute`/`Connected`의 실제 구현 방식이 미확정인 채로 코어 전역에 이미 재사용 확정됨
**위치**: `base/lifecycle-pattern.md` "2026-08-04 검증 라운드에서 보강된
내용" 절, 특히 "`Destroying` 훅은 생각보다 덜 중요할 수 있음" 부분.
**문제**: base는 "이 바인드가 아직 유효한가"를 묻는 인터페이스만 정의하고
quad-roblox가 구현을 채워넣는다고 되어 있는데, 후보 구현 방식들이 서로
다른 타이밍/정확도 보장을 갖는다 — `Instance.Parent == nil` 체크(단순하지만
"일시적으로 부모 없이 옮기는 중"일 때 false positive 위험), 저장해둔
`RBXScriptConnection.Connected`(정확하지만 "무엇에 Connect한 Connection을
기준 삼을지" 별도 결정 필요), `Destroying:Connect`로 세운 플래그(문서가
스스로 "덜 중요할 수 있다"고 약화시킴). 그런데 이 predicate는 이미 Slot
생존 확인·Observer 게이팅·store-bind retract 등 코어 전역에 "canExecute
하나로 통일" 원칙으로 재사용 확정돼 있다 — 즉 여러 하위 시스템이 의존하는
핵심 predicate의 실제 정확도 보장이 아직 안 정해진 채로 그 위에 여러 기능이
이미 "확정"되어 쌓인 상태.
부가적으로, `framework-comparison-findings.md`는 "Vide는 GC와
`Instance.Destroying` 발화 순서가 비결정적이라는 알려진 함정 때문에 의도적
eager cleanup을 택했다"는 구체적 위험을 지적하며 "quad는 rbvm 실물 검증
근거로 이 리스크가 완화됐다"고 적었지만, `lifecycle-pattern.md` 본문
어디에도 **"Destroying 발화 순서 비결정성"이라는 그 구체적 함정에 대한
분석이 없다** — rbvm 검증은 "GC-native 정리가 프로덕션에서 잘 돌아간다"는
것만 보여줄 뿐, "발화 순서가 신뢰 가능한가"라는 별개 질문엔 답하지 않는다.
리스크가 "완화됐다"는 문장이 실제로는 근거 문서 안에서 뒷받침되지 않음.
**제안**: M0 스파이크(또는 M0 직후, M2/M3 착수 전)에서 실제로 어떤 구현이
오탐 없이 동작하는지(특히 Reparent-but-not-Destroy 케이스, 여러 자식이
동시에 Destroy될 때 부모/자식 `Destroying` 발화 순서) 먼저 실측하고, 그
결과로 `lifecycle-pattern.md`의 애매한 서술을 확정 문장으로 교체할 것.
### 1-7. Slot의 `add`/`remove`/`clear` CRUD 의미론이 정의돼 있지 않음
**위치**: `base/slot-plan.md` "개념" 절 — "`add`/`remove`/`clear`/`get`/
`set` 등 뮤터블 연산을 지원하는 메타 배열"이라고만 서술.
**문제**: 실제 시그니처/의미론이 전혀 없다. 예: `remove`는 인덱스를
받는지 값(참조)을 받는지, 존재하지 않는 값을 remove하면 no-op인지
에러인지, `set(i, v)`가 기존 위치의 element를 retract하고 교체하는지,
`clear()` 중간에 개별 element의 retract가 실패(에러)하면 나머지는 계속
처리되는지 등. Observer 콜백이나 store-bind 재실행 안에서 `add`/`clear`가
재진입적으로 호출될 가능성도 있는데, 그 경우의 동작도 무정의.
**제안**: M6(Slot) 착수 시점에 CRUD 각 연산의 인자/반환값/에러 조건을
최소한 표로 확정해둘 것 — 이미 알려진 "여러 Slot 순서 보장" 논의와 같은
타이밍에 같이 정리하면 됨.
### 1-8. Slot "재마운트 시 throw"가 두 가지 다른 추적 대상을 혼용해서 서술됨
**위치**: `base/slot-plan.md` "핵심 제약: 소유권 귀속과 단일 마운트" +
"마운트된 Slot의 재마운트는 즉시 throw" 절.
**문제**: `isMounted`라는 용어가 두 가지 다른 대상에 쓰이는 것처럼 읽힌다
— (a) "한 인스턴스에 대한 다중 마운팅이 절대 일어나지 않도록 강제"는
**Slot에 담기는 개별 child element**가 두 곳에 동시 마운트되는 걸 막는
얘기, (b) "이미 사용된 slot을 재마운트하려 하면 즉시 error()"는 **Slot
컨테이너 자체**가 두 번째 바인드 지점에 쓰이는 걸 막는 얘기다. 둘은 서로
다른 추적 대상(개별 element vs Slot 객체)인데 문서는 하나의 "isMounted
관리"로 뭉뚱그린다. (b)의 트리거 시점도 미정 — `process(inst,k,slotValue)`
가 실제로 호출된 시점(핸들러 매칭)인지, Instance `Parent` 대입까지 끝난
시점인지에 따라 "컴포넌트가 Slot을 prop으로 받아 저장만 하고 실제로는
렌더하지 않는 경로"에서 오탐 throw가 날 수도, 반대로 진짜 이중 마운트를
놓칠 수도 있음.
**제안**: 두 추적을 명시적으로 분리(예: `Slot._mounted: boolean` vs
element별 weak-set) — throw 조건을 "Slot 핸들러의 `process`가 같은 Slot
객체에 대해 두 번째로 불렸을 때"로 명문화. M6 착수 시.
### 1-9. `LifetimeHandle` 인터페이스가 M8에 배치돼 있지만 M4/M6이 이미 그걸 필요로 함(로드맵 순서 역전)
**위치**: `ROADMAP.md` M8 "Ref" — `"LifetimeHandle 인터페이스 + quad-roblox
실제 구현(Instance 생존 확인)"`.
**문제**: `base/lifecycle-pattern.md`("생명 바인드 유틸"을 State-invalidate
리스너 클로저 등록에도 재사용)와 `base/slot-plan.md`(Slot의 `retract`
같은 canExecute 패턴을 그대로 씀)는 둘 다 이 유틸을 State/Store 구독
(M3/M4 영역)과 Slot(M6)에서 이미 쓴다고 명시하는데, `LifetimeHandle`
인터페이스 자체는 M8에서야 정의된다. 즉 M4/M6이 개념적으로 필요로 하는
base 인터페이스가 그보다 늦은 M8에서 만들어지는 순서 역전.
**제안**: `LifetimeHandle.luau`(quad-base, 인터페이스만)를 M2(Dispatch
엔진) 또는 M3(Store/State)로 옮기고, M8은 "quad-roblox 실제 구현(Instance
`Connected` 기반)"만 담당하도록 분리. M1 mock에도 이 인터페이스의 트리비얼
스텁(항상 true)을 붙여두면 M4/M6 테스트가 자연스러워짐.
### 1-10. `store.key`의 레코드 필드 타이핑 검증이 M0가 아니라 M3로 밀려 있음
**위치**: `ROADMAP.md` M0 vs M3 `"store.key dot-access 타입 추론 확인"`.
**문제**: M0의 정의 자체가 "추론만으로 확정하고 실제 Luau로 부딪혀본 적
없는 것"을 검증하는 단계다. `base/store-semantics.md`가 요청한 M0 항목(
"Source가 State를 만족하는 제네릭 메소드 체이닝"의 솔버 안정성)은 이미
반영됐지만, 이건 `:Compute` 같은 제네릭 메소드 체이닝만 다루고 `{key:
Source<number>}` 같은 **레코드 필드로서의 dot-access 타이핑**(읽기/쓰기
대칭성 논거의 핵심 전제)은 별개로 M3에 남아있다. 같은 리스크 카테고리인데
M1(스캐폴딩)·M2(디스패치 엔진) 투자가 먼저 이뤄진 뒤에야 검증되는 셈이라,
여기서 걸리면 이미 만든 스캐폴딩/디스패치 타입 시그니처를 다시 손봐야 할
수 있음.
**제안**: M0 항목에 "`store.key`가 실제로 `Source<T>` 레코드 필드로
안전하게 추론되는지"도 같이 넣을 것 — 어차피 같은 스파이크 파일에서 몇 줄
추가로 검증 가능.
### 1-11. Modifier의 "제네릭 `__index` + `table.clone` 메타테이블 보존" 트릭이 검증 안 된 채 M7 전체 설계의 전제가 됨
**위치**: `base/modifier-plan.md` "런타임은 클래스별 코드 없이 base에 딱
하나만 있으면 됨" 절, `ROADMAP.md` M7.
**문제**: M7의 핵심 주장("base에 제네릭 `__index` 하나면 충분, FrameModifier
류는 순전히 정적 타입 체크용")은 `mod:FontSize(14)` → `__index(self,
"FontSize")`가 즉석 클로저를 리턴하고, `table.clone`이 메타테이블을 그대로
복사해줘서 체이닝이 안 끊긴다는 두 가지 Luau 동작에 전적으로 의존한다.
문서 자체가 "핵심 통찰"이라 부르지만 실제 Luau 코드로 확인된 적은 없다.
이게 틀리면 M7에서 "클래스별 런타임 코드 불필요"라는 설계가 무너지고
필드별 정적 등록 방식으로 되돌아가야 하는데, M7은 M0~M6 다 끝난 뒤라
되돌릴 때 비용이 큼.
**제안**: 검증 비용이 낮음(Modifier 없이도 순수 메타테이블 실험 몇 줄로
가능) — M0 스파이크 후보에 추가하거나, 최소한 "M7 착수 시점에 제일 먼저
확인"이라고 `ROADMAP.md`에 명시.
---
## 우선순위 2 — 지금 결정해두면 싼 것 (지연 결정 리스크)
### 2-1. Source가 State를 만족하는 제네릭 검증이 실패했을 때의 대안(Plan B)이 전혀 없음
**위치**: `base/store-semantics.md` "Source가 State를 만족함" 절 —
"검증 필요(확정 아님, M0 스파이크 대상)... 다만 이것도 추론이라 실제
Luau로 확인 전엔 확정 아님."
**문제**: 검증 필요성 자체는 이미 M0 항목이라 새 지적 아니지만, **검증이
실패했을 때 뭘 하는지가 문서 어디에도 없다.** 이 타입 구조(Source⊂State
서브타입)는 `store.key`의 반환 타입, `:Set()` 문법, `:Emit()`의 위치,
dot-access 타입추론, `RefSource` 폐기 결정까지 전부 이 위에 얹혀 있어서,
Luau 솔버가 막히면 되돌릴 범위가 `store-semantics.md`의 절반 이상에 걸침.
**제안**: M0 스파이크 계획에 "실패 시 폴백은 RefSource 부활 vs 다른 대안"
한 줄이라도 미리 박아두면, 실패했을 때 다시 사용자 자문을 구하느라 멈추는
걸 막을 수 있음.
### 2-2. `State<Modifier>` 타입 차단이 Luau에서 실제로 가능한지 검증 계획이 없음
**위치**: `base/modifier-plan.md` 7번.
**문제**: "가능하면 타입 시스템으로 아예 못 넣게 막을 것"이라 확정했지만,
Luau 제네릭은 "T가 특정 타입이면 거부"하는 부정 제약을 기본 지원하지
않는다. `store-semantics.md`는 이보다 단순한 `Source<T> satisfies
State<T>` 조합조차 M0 스파이크 대상(솔버가 죽을 수 있음)으로 잡아뒀는데,
이보다 어려운 문제(제네릭 타입 파라미터 배제 제약)인 `State<Modifier>`
차단은 어디에도 검증 대상으로 언급되지 않는다. 실제로 안 되면 "UB,
가능하면 타입 차단"이 조용히 "그냥 UB, 런타임 가드 없음"으로 후퇴하는데
그 fallback도 안 적혀있음.
**제안**: `ROADMAP.md` M0(또는 M7 착수 시점)에 이 케이스를 포함하거나,
최소한 `modifier-plan.md`에 "타입 차단이 Luau에서 불가능하면 순수 UB로
폴백"이라는 명시적 fallback 문장을 추가할 것.
### 2-3. Component 래퍼 필요 여부가 "이름만 남음"으로 후순위 처리됐지만 구조적 결정일 가능성
**위치**: `base/component-composition-plan.md` "남은 열린 질문" —
"`Component`(플레인 함수 규약이라 별도 래퍼가 필요한지 자체도 불확실 —
아마 불필요)".
**문제**: 다른 순수 네이밍 항목들과 동급의 후순위로 묶여 있지만, 이건 이름
문제가 아니라 **구조 문제**일 수 있다 — 래퍼가 있고 없고에 따라 (a) 정적
타입 체크 지점(`props.Modifier`/`props.Ref` 필수 필드 검증을 어디서
강제할지), (b) `quad-debug`가 요구하는 컴파일타임 소스 위치 주입(darklua)
훅 지점이 "함수 정의부"가 되는지 "매 호출부"가 되는지가 갈린다. 나중에
"역시 얇은 래퍼가 필요하다"로 뒤집히면, 이미 "그냥 함수" 규약으로 짜인
기존 컴포넌트 전체를 마이그레이션해야 하는 연쇄가 발생.
**제안**: `CLAUDE.md`가 이미 M2/M3/M5에서 quad-debug 훅 확장 지점을
고려하라고 명시해뒀으니, 그 시점에 이 질문도 같이 열어 "래퍼 없음"이
구조적으로도 최종 확정인지 한 번 더 확인할 것. M1 스캐폴딩 전에.
### 2-4. existing-instance-bind가 Slot의 "엄격한 단일 마운트 소유권" 불변식과 근본적으로 긴장
**위치**: `research/existing-instance-bind-plan.md` 전체 vs `base/
slot-plan.md` "핵심 제약: 소유권 귀속과 단일 마운트".
**문제**: 문서가 스스로 언급한 긴장(Modifier flatten의 clone 비용)과는
별개로 더 근본적인 긴장이 있다. `retract`는 "이전에 자신이 process한 것을
무른다"는 전제(quad가 자신이 만든 Instance를 생명주기 끝까지 들고 있는
소유자)로 설계됐는데, existing-instance-bind는 정의상 **quad가 한 번도
process한 적 없는 인스턴스**에 처음 바인드하는 시나리오다. 특히 Slot을 이
인스턴스의 children 제어에 쓰려는 경우 — 기존에 손으로 만들어둔 자식들이
Slot의 "own"한 대상인지 아닌지가 완전히 미정. 문서는 "핸들러 레지스트리가
이미 우선순위 스캔 후 bind 구조라 재바인드도 같은 경로를 타면 됨"이라고
낙관하지만, 이건 "새 값을 process하는 법"만 있으면 된다는 얘기고 "이
인스턴스에 대해 quad가 이전에 뭘 소유했었는지 모르는 상태에서 안전하게
재바인드하는 법"은 다른 문제다. 나중에 "기존 children을 흡수(adopt)하는
API"를 얹어야 할 때 Slot의 "own한 것만 CRUD 대상" 불변식 자체를 건드려야
할 수 있음.
**제안**: 착수 안 해도 되지만, "이 기능이 실제로 필요해지면 Slot의
소유권 모델에 '흡수(adopt)' 개념을 추가해야 할 수도 있다"는 캐비엇을
`existing-instance-bind-plan.md`에 한 줄 추가해둘 것 — 기존 "Modifier
flatten과 긴장" 캐비엇 옆에 병기.
### 2-5. `Modifier.Merge` 시 서로 다른 클래스의 Modifier가 섞이는 게 허용되는지 미정
**위치**: `base/modifier-plan.md` "2. Merge 우선순위" + `base/
component-composition-plan.md` 3번(`Modifier.Merge`).
**문제**: `question.md`가 언급하는 `DI.FrameModifier`류 "클래스별 타입
프리픽스"를 보면 Modifier는 대상 인스턴스 클래스별로 타입이 나뉜다. merge는
"필드명 기준, 나중 게 이김"이라고만 확정돼 있는데 — `Modifier.Merge`
가장 그럴듯한 실사용 시나리오가 바로 "공통 테마 Modifier(여러 GuiObject
클래스에 걸친 공통 필드) + 클래스별 override Modifier"를 합치는 것이다.
두 Modifier가 정적으로 다른 타입이면 `Modifier.Merge` 시그니처가 애초에
타입 에러로 막아주는지(같은 제네릭 파라미터만 merge 허용), 아니면 필드명만
보고 런타임에 섞이는 순수 데이터 레이어라 다른 클래스끼리도 그냥 합쳐지는
지가 전혀 명시돼 있지 않다.
**제안**: Modifier가 target 클래스별 제네릭 타입(`Modifier<Frame>` 등)인지,
있다면 계층 구조(공통 base + 클래스별 확장)가 뭔지, `Modifier.Merge`
이걸 타입으로 강제하는지 최소 한 문장으로 확정할 것. M7 착수 시.
### 2-6. Modifier 필드에 State/Source를 인자로 넘기는 케이스가 세터 표에서 빠짐
**위치**: `base/modifier-plan.md` "4-1. 필드가 State일 수도 있음" 표.
**문제**: 표는 {필드: plain/State} × {인자: 리터럴/함수} 4칸만 다루는데,
"인자 자체가 State/Source"인 경우(예: `mod:FontSize(theme.fontSize)`처럼
이미 반응형인 값을 modifier 필드에 바인딩)가 없다. 상위에서 내려온 테마
색상을 Modifier 필드에 물리는 매우 흔한 패턴일 가능성이 높음. "리터럴"의
정의에 State 핸들도 포함되는지(포함된다면 필드가 State로 교체돼 반응형이
되는지) 불명.
**제안**: 표에 "인자=State" 행 2개(필드 plain/State 각각)를 추가해 명시할
것 — 아마 "clone 후 필드를 그 State로 교체(반응형 획득/전환)"가 자연스러운
답이지만, 함수 인자 케이스(`field:Compute(fn)`)와 어떻게 다른지 분명히 할
것.
### 2-7. 여러 Ref를 하나의 named parameter로 넘길 때 nested-array flatten 여부 불명
**위치**: `base/component-composition-plan.md` 3번(`Modifier.Merge`) vs
"Ref는... 별도 결합 유틸 불필요" 문장.
**문제**: Modifier는 여러 개를 합치려면 `Modifier.Merge`가 명시적으로
필요한데, 바로 다음 문장은 Ref는 "여러 Ref를 받으면 그냥 전부 실행하면
됨 — 별도 결합 유틸 불필요"라고 한다. `props.Ref = {ref1, ref2}`처럼
배열을 넘기면 리프 디스패처가 그 중첩 배열을 재귀적으로 펼쳐서 각 Ref를
인식한다는 뜻인지, 아니면 다중 Ref를 한 named parameter에 담아 넘기는
구체적 방법 자체가 여전히 안 정해진 것인지 불명확. 리프 레벨 디스패처가
배열 위치의 항목을 태그로 판별한다면, 항목 자체가 "배열"일 때 태그가
없어 인식 실패할 가능성이 있음.
**제안**: 리프 디스패처가 중첩 배열을 flatten하는지 명시적으로 확정하고,
다중 Ref를 넘기는 구체적 문법을 한 줄로 못박을 것. M8/M9 착수 시.
### 2-8. Modifier 클래스별 typed 생성자(`FrameModifier` 등)가 M5/M7 로드맵 어디에도 없음
**위치**: `base/modifier-plan.md` "5. 타입 출처는 이미 확정된 dot-access
관습 재사용" 절, `.claude/question.md` 1번.
**문제**: Modifier의 런타임 체이닝 엔진은 quad-base 소유가 맞지만, 클래스별
정적 타입 안전성(`Modifier.Rounded(8)`가 `FrameModifier` 타입으로 추론되는
것)은 "DI 쪽 '제네릭 생성자 함수 하나 + 자주 쓰는 것만 정적 필드' 패턴
재사용"이라 문서 스스로 밝히듯 quad-roblox의 DI 타입 생성 계층(M5)에 강하게
결합돼 있다. 그런데 `ROADMAP.md` M7 체크리스트(flatten-before-dispatch,
`Modifier.Merge`, `State<Modifier>` 차단)엔 이 클래스별 타입 생성 작업이
전혀 없고, M5 DI 체크리스트에도 Modifier 언급이 없음.
**제안**: M5 또는 M7 체크리스트에 "quad-roblox 클래스별 typed Modifier
생성자(FrameModifier 등)" 항목을 명시적으로 추가해 누락을 막을 것.
### 2-9. 컴포넌트가 `props.Modifier`를 받아놓고 forward 안 하면 조용히 드롭됨 — 원칙 명문화 필요
**위치**: `base/component-composition-plan.md` "최종 결론" 1번, 3번.
**문제**: 저작자가 `props.Modifier`를 실수로(또는 의도적으로) 내부
`Frame{...}` 호출에 안 꽂아 넣으면 caller가 넘긴 modifier/ref는 조용히
사라진다. 타입 시그니처에 `props.Modifier: Modifier`가 선언돼 있어도
**실제로 그걸 쓰는지는 런타임/타입 어느 쪽도 강제 안 함** — "받았는데
안 쓰는" 실패 모드가 별도로 존재.
**제안**: 최소한 "무시되면 조용히 드롭된다(UB, 방어 로직 없음)"는 원칙을
명시적으로 못박을 것. 방어할 가치가 있다고 판단되면 컴파일타임 린트
(darklua) 후보로 `quad-debug`/문서화 백로그에 메모.
### 2-10. Tween 자연완료(Completed) 시 per-instance 북키핑 정리 여부가 명세 안 됨
**위치**: `research/tween-plan.md` "`retract`(구 cleanup)로 확정된
오버라이드 시맨틱" 절.
**문제**: "새 값이 들어와 갈아치울 때"의 `retract` 동작(4가지 옵션, 기본값
Cancel)은 상세히 정의했지만, **Tween이 사용자 개입 없이 스스로 끝까지
재생되어 자연 완료된 경우** per-instance weak-keyed 저장소에 남아있는
"이전 Tween 객체" 참조를 어떻게 다루는지는 언급이 없다. 남은 세 오버라이드
옵션(override-without-delete/delete-then-restart/move-to-end) 각각이
"이전 Tween이 아직 재생 중인가, 이미 끝났는가"에 따라 동작이 갈릴 수
있는데(예: "끝점으로 옮기고 새 트윈 시작"은 이미 완료된 Tween엔 의미가
이상해짐), 이 구분 로직 자체가 설계에 없음.
**제안**: Tween 핸들러가 생성한 Tween의 `Completed` 이벤트를 구독해
per-instance 저장소를 정리(또는 상태 플래그 갱신)하는지 여부를 명시.
M11 착수 시.
### 2-11. UI shorthand — 기존 UICorner 자식과의 매칭 기준(이름 vs 타입)이 불명, 사용자 실수 유발 위험 높음
**위치**: `research/ui-shorthand-plan.md` "v1 실제 메커니즘" 절.
**문제**: v1 메커니즘은 "기존 `UICorner` 자식이 있으면 재사용, 없으면
`Instance.new("UICorner", item)`(`Name = "_quad_round"`)"라고 서술되는데,
**이름으로 매칭**(quad가 이전에 만든 `_quad_round`만 재사용)인지 **타입으로
매칭**(자식 중 아무 `UICorner`나 있으면 재사용)인지 불명확. quad-v2 문서가
이 구분을 명시하지 않은 채 그대로 포팅 대상으로 재확정했다. 후자면
사용자가 직접 넣은(quad가 모르는) `UICorner`를 quad가 멋대로 바꿔버리는
부작용 경로가 생기고, 전자인데 사용자가 별도 이름으로 `UICorner`를 하나
더 넣으면 같은 GuiObject에 UICorner가 2개 존재하는 상태(Roblox에서 어느
쪽이 실제로 적용되는지 불명확)가 됨. 다른 항목들과 달리 이건 UB로 방치하기
엔 사용자가 실수하기 매우 쉬운 흔한 시나리오(디자이너가 UICorner를 수동
으로 넣어본 적 있는 프로젝트).
**제안**: "재사용 대상은 quad가 이전에 만든 고정 이름(`_quad_round`류)
자식으로 한정하고, 사용자가 별도로 만든 UICorner와는 아예 상호작용하지
않는다"는 규칙을 명시적으로 확정할 것. M10 전후 착수 시.
---
## 우선순위 3 — 단순화 후보 (사용자 판단 필요)
### 3-1. `:Compute(fn)``previous` 인자 — 클로저 업밸류로 이미 되는 걸 별도 API로 만든 것일 수 있음
**위치**: `base/bind-system-plan.md` "`:Compute(fn)`의 선택적 두 번째
인자 — `previous`" 절.
**문제**: quad는 "렌더 함수가 계속 재실행되지 않고, `Compute`에 전달한
함수 자체가 한 번 등록되어 재계산마다 그 동일 클로저가 재호출된다"는
모델(문서 자체가 명시)다. 그렇다면 사용자가 `fn` 바깥에 `local prev`
업밸류를 두고 `fn` 안에서 그걸 읽고 갱신하면, 별도 `previous` 파라미터
없이도 정확히 같은 "직전 반환값 재사용" 효과를 순수 Lua 문법만으로 얻을
수 있어 보인다. 그런데 이 문서는 `previous`**별도 API 표면**(두 번째
인자)으로 만들었고, "능동적으로 계속 관측되지 않으면 조용히 영구
정지한다"는 상당히 위험한 캐비엇까지 별도로 문서화해야 할 만큼 무거운
기능이다. 왜 클로저 업밸류로 충분하지 않은지 근거가 안 보임.
**제안**: `previous` 인자를 유지할 근거(예: 업밸류 방식보다 타입 추론이
쉬워진다든가)가 있다면 한 줄 추가하고, 없다면 "그냥 클로저 업밸류를
쓰라"는 문서화 패턴으로 대체해 API 표면 자체를 줄이는 걸 검토.
### 3-2. Corner/PaddingAll/Scale 개별 Handler 대신 데이터 테이블 구동 단일 제네릭 Handler
**위치**: `research/ui-shorthand-plan.md` "메커니즘 — 새 아키텍처 개념
불필요" 절.
**문제**: 문서는 "Corner/PaddingAll/Scale 같은 특수 키를 인식하는
Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴 값 하나 →
고정 이름 자식 찾기/생성 → 프로퍼티 세팅)의 Handler를 각각 만드는
그림이다. 문서 자체가 "앞으로 비슷한 제안이 오면 이 선례를 따르라"고
일반화하고 있어, 향후 비슷한 shorthand가 추가될 때마다 Handler 파일이
선형으로 늘어나는 구조.
**제안**: `{key -> {ChildClassName, ChildDefaultName, Property, wrap=fn}}`
형태의 룩업 테이블 하나로 구동되는 단일 `Handlers/InstanceShorthand.luau`
로 통합하는 안을 검토. 새 shorthand 키 추가가 "테이블에 항목 하나 추가"로
끝나 M10 이후 유지보수 비용이 줄어듦. 강제 사항 아님, 구현 시점에 결정할
정도의 사소한 개선 후보.
---
## 문서 모순 — 발견 현황
### 이미 고침 (이번 세션)
- `architecture.md`의 "Store/State/Source 온톨로지 확정 요약" 절이
`store-semantics.md`의 최신 재구성(Source가 State를 만족, `store.key`
Source를 직접 반환, `store.key:Set()`)을 못 따라가고 있던 것 — 이 감사
세션 도중 발견해 직접 정정(커밋 `4b839b0`에서 별도로 이미 반영됨을 뒤늦게
확인 — 같은 문제를 두 세션이 독립적으로 발견한 셈).
- `architecture.md` 소스트리의 `Store.luau`/`Ref.luau` 주석이 각각 옛
`__newindex` 모델, 옛 "Ref=CreatedRef 자체" 정의를 그대로 담고 있던 것 —
이번 세션에서 직접 정정.
### 아직 안 고침 (판단 필요해서 여기 남김)
- **`State<Modifier>` 타입 차단(엔지니어링 비용 감수) vs Ref/Slot이
Modifier 필드에 들어가는 건 UB로 방치 — 같은 문서 안에서 정반대 원칙이
나란히 적용됨.** `base/modifier-plan.md` "Modifier는 핸들러 계층을
모름" 절은 "권장 사용법은 아니지만 막을 이유도 없음 — 방어 로직 없는
UB로 남겨둠"이라 명시적으로 방어를 포기했는데, 바로 옆 7번 절은
`State<Modifier>` 조합을 "UB로 확정, **가능하면 타입 시스템으로 아예 못
넣게 막을 것**"이라며 정반대로 엔지니어링 비용을 들여 방어하기로 했다.
두 결정 다 나름의 근거(후자는 "State에 담기면 재-flatten이 필요해져서
정적 merge 전제와 정면 충돌"이라 더 위험하다는 논리로 보임)가 있어
보이지만, 문서 어디에도 "왜 이 경우엔 원칙에서 예외로 처리하는가"를
명시적으로 인정/정당화하지 않고 그냥 나란히 적혀 있다. 위 2-2 항목
(Luau에서 실제 차단 가능한지)과 묶어서 같이 정리할 문제.
- **Destroying 훅 신뢰도에 대한 서술이 `lifecycle-pattern.md` 내부에서도,
`framework-comparison-findings.md`와의 사이에서도 어긋남** — 위 1-6
항목에 상세, 여기서는 "아직 아무도 하나의 확정 문장으로 정리 안 함"이라는
사실만 문서모순 항목으로 남겨둠.
---
## 참고 — 감사했지만 문제없다고 확인된 것
- `component-composition-plan.md`가 예전 `StoreSource`/`RefSource`
개념을 참조하는 채로 남아있진 않은지 확인 — 이미 "Source가 State를
만족함" 최신 모델로 정정돼 있어 문제없음.
- `ROADMAP.md``store.key = value`(구 `__newindex`) 모델을 암시하는
잔여 표현은 없음 — M3/M4 서술 모두 문법을 명시하지 않아 최신 `:Set()`
모델과 직접 충돌하는 곳은 없음.
- M9(컴포넌트 합성)이 M7(Modifier)·M8(Ref) 뒤에 오는 순서 — M9는 "M0
스파이크(named-parameter 전달)를 정식 Modifier/Ref로 검증"하는 단계라고
명시돼 있어 뒤늦은 검증이 아니라 의도된 정식화.
- `PerInstanceState` 실제 구현 시점(M8) — 이걸 필요로 하는 핸들러(Tag/
Attribute/Tween)가 전부 M10/M11이라 순서상 문제없음.
- Slot의 store-bind 의존(M6→M4) 순서.
---
## 다음 액션 제안
- **M0 착수 전**: 1-5(props.Modifier/Ref nil-hole)를 M0 스파이크 코드에
반영, 1-10(`store.key` 레코드 필드 타이핑)을 M0로 앞당기는 것 검토,
1-11(Modifier `__index` 트릭)도 비용이 낮으니 M0 후보로 포함 검토.
- **M2(Dispatch) 착수 전**: 1-2, 1-3, 1-4를 한 번에 확정(전부 base
dispatch 엔진의 에러/상태관리 규칙이라 같이 결정하는 게 효율적).
- **M2/M3 착수 전**: 1-6(canExecute 실제 구현) 실측, 1-9(LifetimeHandle
마일스톤 재배치).
- **나머지**: 해당 마일스톤 착수 시점에 이 문서를 다시 열어 관련 항목만
확인하면 됨 — 지금 전부 결정할 필요는 없음.

178
CLAUDE.md
View file

@ -38,12 +38,19 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
`.claude/README.md`가 색인. 요약:
- `.claude/base/` — 확정된 아키텍처/컨텍스트, plan/done 개념 없음. 먼저
`.claude/base/architecture.md`를 읽을 것.
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의. 지금은
`tween-plan.md`(세부 옵션만 남음), `existing-instance-bind-plan.md`(급하지
않음) 두 개뿐 — `component-composition-plan.md`는 2026-08-04 세션에 수렴
완료돼 `base/`로 승격됨.
- `.claude/qa-request/`, `.claude/archive/`, `.claude/feedback/` — 구현
시작되면 쓰기 시작함, 지금은 비어있음.
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의.
`tween-plan.md`/`existing-instance-bind-plan.md`/`debug-tooling-plan.md`/
`ui-shorthand-plan.md`/`documentation-plan.md`/`documentation-content-map.md`/
`framework-comparison-findings.md` — 전부 후순위(급한 건 `tween-plan.md`
세부 옵션 정도). 최신 목록·우선순위는 `.claude/README.md`가 소스, 여기서
개수 반복 안 함(과거에 "두 개뿐"이라 적어놨다가 새 문서 추가될 때마다
안 갱신되는 패턴이 반복돼서 아예 안 세기로 함).
- `.claude/qa-request/`, `.claude/feedback/` — 구현 시작되면 쓰기 시작함,
지금은 비어있음. `.claude/archive/`는 원래 같은 취급이었으나
2026-08-06 세 번째 세션부터 **완전히 뒤집힌 설계 결정을 원문+역전
이유+diff와 함께 보존하는 용도로도 사용 시작**(구현 완료 대상만이
아님) — `archive/store-source-proxy-reversed.md`가 첫 사례, 나중
`quadnomicon` 콘텐츠 소재로 재사용 예정.
- `.claude/initreq/` — 클론해둔 참고 레포(quad v1, Fusion, Vide, rbvm, tbox,
code-docker) + PA님 실 코드(`artworks/`) + 원본 요청. **읽기 전용,
`.gitignore`로 커밋 제외됨** — 내용을 다른 곳으로 옮기지 말고 항상 원본
@ -102,7 +109,11 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
quad-base용 최소 mock 테스트 하네스)으로 진행 — 소스 트리 자체는 이미
확정됨(`base/architecture.md` "구현 착수" 절). 이 단계부터
`qa-request/`/`archive/` 폴더가 실제로 쓰이기 시작함. **세부 순서/todo는
`ROADMAP.md`가 소스** — 여기서 반복 안 함.
`ROADMAP.md`가 소스** — 여기서 반복 안 함. **M0 착수 직전에 `research/
pre-implementation-audit.md`(2026-08-06 신설)의 우선순위1 항목부터 먼저
확인할 것** — 특히 M0 스파이크 코드 자체에 반영해야 할 항목(props.Modifier/
Ref를 안 넘기는 케이스 포함, `store.key` 레코드 필드 타이핑도 M0로
앞당기기 검토)이 있음, 아래 최신 세션 요약 참고.
2. **용어 정리 — 사용자가 별도로 요청, 진행 중.** "register"(v1) 같이
부정확한 이름들을 전체적으로 재검토하자는 요청 — 1차 제안 완료(우선순위
순: `State`가 React/Vue식 "쓸 수 있는 로컬 상태"라는 통상 의미와 반대라
@ -121,9 +132,13 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
채널 실현 가능성(Studio 플러그인↔Play 중 게임 간 BindableEvent/Function
통신)까지 실측 검증 완료, 세부 API 이름만 남음. 착수 시점은 여전히
"quad 개발 상당 부분 끝난 뒤"로 사용자가 못박음. 같은 날 파생된 문서화
전략 뼈대(`research/documentation-plan.md` — UI 네이밍 컨벤션, Store
부작용 게임 시스템 활용 패턴, 권장 이벤트 핸들링 패턴 3종)도 후순위
백로그로 같이 남김.
전략(`research/documentation-plan.md` — UI 네이밍 컨벤션, Store 부작용
게임 시스템 활용 패턴, 권장 이벤트 핸들링 패턴 3종)도 후순위 백로그로
같이 남김. **[2026-08-06 세 번째 세션에서 크게 확장됨]** 문서 사이트
전체 구조(초심자/api/심화/`quadnomicon` 4축)와 실제 콘텐츠 분류맵
(`research/documentation-content-map.md`), quad vs Fusion/Vide/react-lua
정직 비교(`research/framework-comparison-findings.md`)까지 늘어남 —
착수 우선순위 자체는 안 바뀜(여전히 후순위), 아래 최신 세션 요약 참고.
5. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
(`HUMAN_TODO.md` 2번 항목).
@ -411,3 +426,146 @@ Ref 일반화 — 네 가지 다 확정, 실제 base 문서에 반영 완료.**
쌈). 내부 레지스트리는 자동 케이스의 weak table과 별개로 강참조
(weak면 "살려둔다"는 목적이 무의미해짐). 둘 다 idempotent, `:Unsubscribe()`
자동 케이스 조기 해제에도 재사용.
## 2026-08-06 세 번째 세션 — 문서 사이트 구조, 프레임워크 정직 비교, Source가 State를 만족하는 서브타입 재구성
같은 날 이어진 세 번째 세션. 셋으로 갈리는 주제라 순서대로 요약 — **다음
세션이 새로 알아야 할 건 4번(Source/State 재구성)뿐**, 1~3번은 배경/참고용.
**1. 문서 사이트 구조 확정 — 초심자/api/심화 3축 + `quadnomicon` 4번째 축.**
`research/documentation-plan.md` 0번 항목에 전부 반영. 초심자는 "core loop
완주에 필요한 최소 집합만, 백엔드 구체적(quad-roblox), quad-base/roblox
분리 노출 안 함, 다른 백엔드 생기면 그때 별도 트랙 추가"로 스코프 확정.
api는 간략 설명 + 심화로 "더 알아보기" 링크 패턴. `quadnomicon`(Rustonomicon
패러디, 사용자 확정 이름)은 quad 사용자가 아니라 "비슷한 프레임워크를
설계/포크하려는 엔지니어"용 4번째 축 — Fusion/Vide 내부 비교 같은 콘텐츠가
여기 해당, 세션 정정 이력 같은 순수 내부 리서치 원자료는 이 축에도 안
들어가고 그냥 `.claude/` 내부에만 영구히 남음(RFC 저장소 성격). GC처럼
quad 밖 배경지식이 깊은 주제는 새 티어 없이 "quad 활용법만 심화에, 일반
개념은 외부 링크"로 처리. 실제 콘텐츠 분류(초심자 core loop 목차 초안,
파일별 분류, 심화 에세이 후보 15개)는 `research/documentation-content-map.md`.
**2. quad vs Fusion/Vide/react-lua 정직 비교 — 3개 에이전트가 실제
소스/웹 리서치로 검증.** `research/framework-comparison-findings.md`.
요지: quad의 Slot 단일 마운트 가드·열린 우선순위 축·명시적 의존성·다이아몬드
dedup은 실 소스 근거로 확인된 진짜 강점(Fusion `Children.luau`의 TODO
주석, Vide `mount.luau`의 중복 체크 부재, Vide 자신이 `todo.md`에 미해결로
남긴 diamond 문제 등). 반대로 use-after-destroy 검증 안전망 부재·`:With`
정적 의존성·Store dot-access 할당 비용 3가지는 고칠 만한 약점으로 식별(3번은
이후 4번 논의로 이미 해소됨). GC-native 리스크·암묵 추적 대비 보일러플레이트·
Tween 비합성성·"지금 트리 상태" 파악 어려움은 의도된 트레이드오프로 "고친다"
개념 자체가 안 맞음. 성숙도 격차(quad 구현 0줄)는 정직하게 명시.
**3. 위 1·2번에서 파생된 실행 항목**: 아직 결정 아님, `research/
documentation-plan.md`/`framework-comparison-findings.md`의 "다음 단계"에
남겨둔 사용자 판단 대기 항목들(문서화 착수 시점, 프레임워크 비교에서 나온
개선안 반영 여부/시점) 그대로 참고.
**4. Source가 State를 구조적으로 만족 — Store/State/Source 핵심 메커니즘
재구성, base 문서 전부 반영 완료.** `store.key`의 타입 문제(레코드 타입
`{key: State<number>}`가 읽기/쓰기 비대칭이라 Luau 타이핑이 안 맞음)를
풀다가 나온 더 근본적인 재구성:
- **`Source<T>`가 구조적으로 `State<T>`를 만족**(단방향 호환, Svelte
`Writable<T> extends Readable<T>`와 같은 모양) — `.value`/`:Get()`/
`:With`/`:Compute` 전부 지원 위에 `:Set(value)`/`:Emit()` 추가. `:With`/
`:Compute`는 Source에서도 항상 `State<U>` 반환(구현은 metatable `__index`
델리게이션, `Modifier`의 제네릭 `__index` 트릭과 같은 패턴이라 로직
중복 없음). 이 서브타입 관계는 `quad2-try`에서 기각한 컴포넌트/클래스
OOP 상속과 다른 층위(프리미티브 타입 간 구조적 서브타이핑일 뿐, 사용자가
짜는 클래스 계층 구조가 아님)라 그 금지와 안 부딪힘.
- **`RefSource`(store 슬롯 전용 타입 중간안)와 그 전신인 `StoreSource`
프록시(2026-08-04 세션에서 confirmed였던 것)는 전부 폐기.** Store는
이제 "이름 붙은 Source 모음, 그 이상 아님" — `store.key`는 Store 생성
시 이미 만들어둔 진짜 Source 객체를 그대로 반환(별도 wrapper 생성/캐싱
단계 자체가 사라짐, 이전에 검토한 "State를 weak table로 캐싱"보다도
쌈). v1이 타입 없던 시절 습관으로 모든 값을 Store에 몰아넣은 건 "당시엔
편해서"였지 지금 그대로 가져올 이유가 아니라는 게 사용자의 회고적
재평가 — 그 재검토가 이번 단순화로 이어짐.
- **`store.key = value`(`__newindex`) 폐기, `store.key:Set(value)`
전환** — 이유 둘: (a) 레코드 타입 `{key: Source<number>}`가 읽기/쓰기
둘 다 같은 타입이어야 Luau 타이핑이 깨끗한데 대입 문법을 유지하면
비대칭이 남음, (b) `=`는 관례상 "즉시 커밋되는 부작용 없는 쓰기"를
암시하는데 quad는 실제로 lazy(무효화 신호만 쏘고 재계산은 관측 시점에)라
대입 문법이 실제 동작과 정서적으로 안 맞음(사용자 논거). `Store:Emit(key)`
같은 이유로 `source:Emit()`(key 인자 불필요)로 이동 — 같은 일 하는
두 번째 경로를 안 남긴다는 원칙과 일치.
- **검증 필요, M0 스파이크에 항목 추가됨(`ROADMAP.md`)**: Source의
`:Compute` 시그니처가 자기 자신과 `State<U>`를 동시 참조하는 제네릭
메소드라 Luau 솔버가 재귀 타입 조합에서 안 막히는지 확인 필요. 자기
참조 self 타이핑 자체는 흔하고 안전하나, `State<T>`가 거꾸로 `Source`
참조하는 **상호 재귀**는 Luau의 알려진 취약 패턴이라 피해야 함 —
`State<T>``Source` 참조 없이 독립적으로 먼저 정의하고 `Source<T>`
단방향으로 `State<T>`를 참조하게 두면 이 위험을 피할 수 있어 보이나
확정 아님. 타입은 `&`(교차) 조합 대신 손으로 펼쳐 쓰는 쪽으로(사용자
선호, 솔버 안정성 우선) — 이건 런타임 구현 델리게이션과 다른 축이라
서로 안 부딪힘(타입은 펼치고 구현은 공유 가능).
- **반영된 파일**: `base/store-semantics.md`(신규 "Source가 State를
만족함" 절이 최종 소스), `base/bind-system-plan.md`(온톨로지·타입 추론
절 정정), `base/component-composition-plan.md`(`StoreSource`/타입
유니온 절 재작성), `ROADMAP.md`(M0 항목 추가), `research/
documentation-content-map.md`/`.claude/README.md`(참조 갱신). 이름
자체(`Source`/`State`)는 여느 때처럼 "지금 할 일" 2번 용어 정리
라운드까지 가칭.
## 2026-08-06 네 번째 세션 — M0 착수 직전 크리티컬 감사, `research/pre-implementation-audit.md` 신설
사용자 요청: "실 개발 시 모호하여 인터럽트될 수 있는 부분, 나중에 결정되면
치명적일 것 같은 것, 지금 구조가 오버엔지니어링일 수 있어 보이며 더 나은
대안이 있는 것"을 찾아 정리해달라는 요청. `.claude/base/` 전체(architecture/
bind-system/store-semantics/module-lifecycle/component-composition/
modifier/purity-and-effects/slot/lifecycle-pattern/quad-v1-architecture)와
근접 `research/`(existing-instance-bind/tween/ui-shorthand) + `ROADMAP.md`
4개 클러스터로 나눠 서브에이전트 4개를 병렬로 돌려 "모호성/지연결정리스크/
단순화후보" 세 렌즈로 재감사, 결과를 `research/pre-implementation-audit.md`
로 종합. `.claude/question.md`엔 이미 취합된 것(용어 재검토, M0 스파이크
항목 자체 등)과 겹치지 않는 새 발견만 반영.
**작업 도중 발견한 부수 이슈**: 워크트리 생성 시점과 main 체크아웃의
미커밋 변경사항(세 번째 세션 결과물)이 어긋나 있었음 — 워크트리는 커밋
시점 기준으로 fork되므로 아직 커밋 안 된 변경은 안 딸려옴. 사용자가 중간에
main에 커밋을 완료해줘서(`4b839b0`) 워크트리를 새로 만들어 재동기화함 —
**앞으로 워크트리에서 최신 설계를 감사/참조해야 하는 작업을 시작하기 전엔,
main에 미커밋 변경이 있는지(`git status`) 먼저 확인하고 필요하면 커밋을
요청하거나 파일을 직접 동기화할 것.**
**핵심 발견 요약** (전체 25개 항목은 `pre-implementation-audit.md` 참고,
우선순위1만 발췌):
- **Tween.luau가 문서 전체에서 "범용 store-bind 캐치올 핸들러"의 유일한
구체 예시로 서술됨** — 애니메이션 없는 일반 반응형 프로퍼티 바인딩이
실제로 Tween 파일을 거쳐가는지, 별도 범용 핸들러가 필요한지 확정 안 됨.
가장 구조적인 발견 — 직접 `bind-system-plan.md` 67-79행을 재확인해
agent 발견을 검증함.
- `props.Modifier`/`props.Ref` forwarding 관례가 Lua 배열 리터럴의
nil-hole 함정(caller가 안 넘기면 `{nil, ref, child}`에서 뒤 항목까지
무시될 수 있음)에 그대로 노출 — M0 스파이크 코드에 이 케이스를 반드시
포함시켜야 함.
- `canExecute`/`Connected`의 실제 구현 방식이 미확정인 채 코어 전역
(Slot/Observer/store-bind retract)에 이미 재사용 확정돼 있음.
- `LifetimeHandle` 인터페이스가 M8에 배치돼 있지만 M4/M6이 이미 그 인터
페이스를 전제로 서술돼 있음 — 로드맵 순서 역전, `ROADMAP.md` 조정 필요.
- retract 시 "이전에 실제로 매치됐던 핸들러" 추적 책임, 우선순위 스캔
동률/매치실패 처리, provider 미주입 상태 dispatch 호출 시 동작 —
전부 M2(Dispatch 엔진) 착수 전 한 번에 결정하면 효율적인 것들.
- Slot의 `add`/`remove`/`clear` CRUD 의미론 자체가 정의 안 돼 있음,
"재마운트 시 throw"도 추적 대상(개별 element vs Slot 컨테이너)이
뭉뚱그려 서술됨 — 둘 다 M6 착수 전 확정 필요.
**단순화 후보로 지적된 것 중 사용자 판단 필요**: `:Compute(fn)`
`previous` 두 번째 인자 — quad의 "함수 자체가 재호출되는" 모델상 클로저
업밸류로 이미 되는 걸 별도 API 표면으로 만든 것일 수 있음(근거 불명).
**문서모순으로 남겨둔 것**: `State<Modifier>`는 "UB, 가능하면 타입으로
차단"인데 Ref/Slot이 Modifier 필드에 들어가는 건 "UB, 방어 로직 없음" —
같은 문서(`modifier-plan.md`) 안에서 정반대 원칙이 근거 설명 없이 나란히
적용됨. 판단이 필요해 고치지 않고 감사 문서에만 남김.
**부수적으로 직접 고친 stale 문서(판단 불필요한 순수 동기화)**: `base/
architecture.md` 소스트리 주석 두 곳 — `Store.luau`가 여전히 옛 `__newindex`
모델을 언급, `Ref.luau`가 여전히 "CreatedRef 메커니즘 자체"로만 서술(Ref
일반화 결정 반영 안 됨). 온톨로지 요약 절 stale은 같은 세션 도중 커밋
`4b839b0`에서 이미 독립적으로 고쳐져 있었음을 확인 — 재작업 없이 스킵.
**다음 세션이 할 일**: M0 착수 전에 `pre-implementation-audit.md` 우선순위1
항목(특히 위 6개)부터 확인 — "지금 할 일" 1번 참고. `.claude/question.md`
2번에 사용자 판단이 필요한 항목 요약이 반영돼 있음.

View file

@ -17,6 +17,12 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
- [ ] Store/State push-invalidate → pull-recompute propagation을 실제로
짜보기(다이아몬드 의존성 케이스 포함 — 이미 invalid면 전파 중단되는지)
- [ ] Source가 State를 구조적으로 만족하는 제네릭 타입(`:Compute<U>(self:
Source<T>, ...) -> State<U>`류, self 타이핑 + State 참조 혼합)이
Luau 솔버에서 안전하게 추론되는지 확인(2026-08-06 후속 세션,
`base/store-semantics.md` "Source가 State를 만족함" 절 — `State<T>`
`Source`를 참조하지 않는 단방향 의존으로 두면 위험한 상호 재귀는
피할 수 있어 보이나 실제 검증 전엔 확정 아님)
- [ ] `process`/`retract` 재귀 재-process 디스패치를 실제로 짜보기(store-bind
핸들러 하나 + `isHandlable` 우선순위 스캔 포함)
- [ ] `props.Modifier`/`props.Ref` named-parameter로 받는 컴포넌트 하나 작성,