merge: main의 최신 리서치/감사 반영 (문서사이트 구조, Source/State 서브타입 재구성, M0 크리티컬 감사)
# Conflicts: # .claude/README.md
This commit is contained in:
commit
6c99de744d
13 changed files with 1476 additions and 91 deletions
|
|
@ -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` 소재 후보 |
|
||||
|
||||
## 참고
|
||||
|
||||
|
|
|
|||
73
.claude/archive/store-source-proxy-reversed.md
Normal file
73
.claude/archive/store-source-proxy-reversed.md
Normal 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`에서 "설계가 왜 이렇게
|
||||
반전됐는가" 사례로 쓰기 좋음.
|
||||
|
|
@ -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/로 분리됨)
|
||||
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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`는 확인 불필요로 재확인** — 진행이 중단된 상태라 이 논의와
|
||||
무관.
|
||||
|
|
|
|||
|
|
@ -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) — 확정
|
||||
|
|
|
|||
|
|
@ -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` — 스코프 논의만 필요, 구현
|
||||
착수를 막지 않음.
|
||||
|
|
|
|||
174
.claude/research/documentation-content-map.md
Normal file
174
.claude/research/documentation-content-map.md
Normal 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번 리스트를 심화 섹션
|
||||
에세이 백로그로 사용.
|
||||
|
|
@ -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번 —
|
||||
|
|
|
|||
110
.claude/research/framework-comparison-findings.md
Normal file
110
.claude/research/framework-comparison-findings.md
Normal 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 콘텐츠 소재로 재사용 가능.
|
||||
567
.claude/research/pre-implementation-audit.md
Normal file
567
.claude/research/pre-implementation-audit.md
Normal 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
178
CLAUDE.md
|
|
@ -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번에 사용자 판단이 필요한 항목 요약이 반영돼 있음.
|
||||
|
|
|
|||
|
|
@ -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로 받는 컴포넌트 하나 작성,
|
||||
|
|
|
|||
Loading…
Reference in a new issue