문서 사이트 구조/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:
parent
e58ff06012
commit
4b839b09e1
11 changed files with 805 additions and 87 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,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`
|
||||
|
|
|
|||
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`에서 "설계가 왜 이렇게
|
||||
반전됐는가" 사례로 쓰기 좋음.
|
||||
|
|
@ -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) — 확정
|
||||
|
|
|
|||
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 콘텐츠 소재로 재사용 가능.
|
||||
109
CLAUDE.md
109
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`로 커밋 제외됨** — 내용을 다른 곳으로 옮기지 말고 항상 원본
|
||||
|
|
@ -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번 용어 정리
|
||||
라운드까지 가칭.
|
||||
|
|
|
|||
|
|
@ -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