문서 사이트 구조/quadnomicon 신설, 프레임워크 정직 비교, Source가 State를 만족하는 서브타입 재구성

세 갈래 작업:

1. 문서 사이트 구조 확정(초심자/api/심화 3축 + quadnomicon 4번째 축) —
   research/documentation-plan.md 0번 항목, research/documentation-content-map.md
   신설(초심자 core loop 목차 초안, 파일별 분류, 심화 에세이 후보 15개).

2. quad vs Fusion/Vide/react-lua 정직 비교 — research/framework-comparison-findings.md
   신설. 3개 에이전트가 실제 소스(Fusion/Vide 로컬 클론)+웹 리서치(react-lua)로
   검증. quad 강점(Slot 단일 마운트 가드, 열린 우선순위 축, 명시적 의존성,
   다이아몬드 dedup)과 고칠 만한 약점 식별.

3. Source가 State를 구조적으로 만족하는 서브타입으로 재구성(핵심 변경) —
   store.key 타입 문제(레코드 타입 읽기/쓰기 비대칭)를 풀다가 StoreSource
   프록시 설계(2026-08-04 확정분)를 완전히 대체:
   - Source<T>가 State<T>를 구조적으로 만족(단방향 호환), Store는
     "이름 붙은 Source 모음"으로 단순화 — 별도 wrapper 생성/캐싱 불필요
   - store.key = value(__newindex) 폐기 → store.key:Set(value)
   - Store:Emit(key) → source:Emit()
   - base/store-semantics.md에 새 절로 반영, bind-system-plan.md/
     component-composition-plan.md/architecture.md 정정
   - ROADMAP.md M0에 Luau 솔버 검증 항목 추가(재귀 타입 조합)
   - 폐기된 StoreSource 원문은 archive/store-source-proxy-reversed.md에
     역전 이유·신구 비교와 함께 보존(quadnomicon 소재 후보)

전체 코퍼스 stale 참조 재점검: architecture.md 요약절, README.md 승격 누락,
Modifier UB 규칙 확장 등 발견해서 수정.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-06 21:29:56 +09:00
parent e58ff06012
commit 4b839b09e1
Signed by: qwreey
GPG key ID: D28DB79297A214BD
11 changed files with 805 additions and 87 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,18 @@
|---|---|---|
| `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 전후 구현하면 됨 |
## `archive/` — 완료됐거나 완전히 뒤집힌 것, 능동 참고 불필요
| 문서 | 내용 |
|---|---|
| `store-source-proxy-reversed.md` | 2026-08-04에 확정했던 `StoreSource` 프록시 설계(Store가 Source를 감춘 별도 프록시로 감쌈) — 2026-08-06 세 번째 세션에서 "Source가 State를 구조적으로 만족" 재구성으로 완전히 대체됨. 원문·역전 이유·신구 비교표 보존, `quadnomicon` 소재 후보 |
## 참고
- **저장소 소유자가 답해야 할 질문 전체 취합**: `.claude/question.md`

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

@ -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

@ -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 콘텐츠 소재로 재사용 가능.

109
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`로 커밋 제외됨** — 내용을 다른 곳으로 옮기지 말고 항상 원본
@ -121,9 +128,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 +422,83 @@ 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번 용어 정리
라운드까지 가칭.

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로 받는 컴포넌트 하나 작성,