설계 검증 라운드(2026-08-04) 결과 반영

2026-08-03 확정 사항 전체를 AskUserQuestion으로 하나씩 재검증. 대부분
그대로 확인됐으나 State 프리미티브 존재 여부(있어야 함으로 정정), Pipe
copy-on-write 후보(폐기, state(state) 조합으로 대체), Slot retract 시
동작(폐기로 확정) 등 실제 정정이 발생 — Store/State/Source 온톨로지가
다음 세션 최우선 열린 설계 스레드로 새로 부상.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-04 12:06:26 +09:00
parent 0c9b8584ee
commit 0dbbc3d0b1
Signed by: qwreey
GPG key ID: D28DB79297A214BD
9 changed files with 294 additions and 63 deletions

View file

@ -28,13 +28,13 @@
| `quad-v1-architecture.md` | v1(`initreq/quad`) 내부 동작 스냅샷 — "이 문제를 안 반복하려면"의 기준선 |
| `comparison-fusion-vide.md` | Fusion/Vide 아키텍처 비교 리서치 — 설계 결정 근거 자료 |
| `lifecycle-pattern.md` | rbvm의 `Connected`+GC 관용구를 quad-v2가 채택하는 방식 |
| `store-semantics.md` | Store는 부작용 허용이 기본, 별도 State 프리미티브는 안 만듦 |
| `store-semantics.md` | Store는 부작용 허용이 기본. State는 Store 위의 조합 가능한 캐시 레이어로 실제로 필요함(2026-08-04 정정) — Store/State/Source 온톨로지는 `research/bind-system-plan.md`에서 진행 중 |
## `research/` — 아직 착수 전, 상의 필요
| 문서 | 내용 | 우선순위 |
|---|---|---|
| `bind-system-plan.md` | pluggable key/value 핸들러 레지스트리 — `process`/`retract` 디스패치 모델, Ref, quad2-try 리서치 결과. 핵심은 확정, 세부 시그니처만 남음 | 최상 — 다른 모든 설계가 이 위에서 조립됨 |
| `bind-system-plan.md` | pluggable key/value 핸들러 레지스트리 — `process`/`retract` 디스패치 모델(확정), Ref(확정), quad2-try 리서치 결과. **Store/State/Source 온톨로지는 2026-08-04에 새로 열린 미해결 설계 스레드**(다음 세션 최우선) | 최상 — 다른 모든 설계가 이 위에서 조립됨 |
| `module-lifecycle-plan.md` | 프로바이더 패턴, bind/store 구현 책임 분리 — 확정됨 | 최상 — 확정, 구현 착수 시 API 세부만 조정 |
| `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw | 상 — bind-system 확정 후 |
| `tween-plan.md` | 트윈을 Store 밖 특수 bind key로 처리, 기본 오버라이드는 Cancel | 중 — 세부 옵션만 남음 |

View file

@ -54,9 +54,9 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo
별개 라이브러리로 존재해야 함(v1 `lang.lua`의 전역 스코프 버그 등은
`base/quad-v1-architecture.md` 참고 — 애초에 반면교사).
11. **커스텀 Signal 클래스 미구현.** 콜백 정도로 이벤트 바인드 뒤에 함수를
넣는 것만으로 충분하다고 판단(단, `base/lifecycle-pattern.md`의 rbvm 리서치
결과 rbvm의 커스텀 Signal이 실제로는 재사용 가능해 보여서 상충 — 열린 질문으로
`.claude/question.md`에 있음).
넣는 것만으로 충분하다고 판단. (이전 초안엔 "rbvm의 Signal이 재사용
가능해 보여 상충한다"는 메모가 있었으나 2026-08-04 검증 라운드에서 최종
확정으로 재확인 — 더 이상 열린 질문 아님.)
12. **멀티 타겟(pluggable 백엔드) — 특히 GTK 지원까지 염두.** Roblox 전용 렌더
기술(react.lua, Fusion)은 결국 외부 개발자 유인이 없어 발전이 더딜 거라는
문제의식. 결과적으로 `plug/roblox`, `plug/base` 정도로 나뉠 전망 — base가
@ -75,3 +75,11 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo
바인드 시스템 디스패치, Slot 설계 세부, Tween 플러깅, 모듈 라이프사이클/누가
Store를 구현하는가, 순수함수 범위, 이미 생성된 인스턴스에 대한 바인드 —
`.claude/research/` 각 문서 참고, 전체 색인은 `.claude/README.md`.
**가장 시급한 미정 사항(2026-08-04부터, 다음 세션 최우선)**: Store/State/
Source 온톨로지 — Store는 source(실제 값이 존재하는 단일 지점) 집합체이고,
`store "key"`처럼 접근할 때마다 그 source를 감싸는 새 State(자기 고유
value 없는 조합 가능한 캐시)를 반환한다는 모델까지는 나왔으나, `:Compute`
캐싱/무효화 전략, Luau 타입 시스템에서 커링 호출의 타입 추론 문제 등 세부는
전부 열려있음. `research/bind-system-plan.md`의 "Store/State/Source
온톨로지" 절, `.claude/question.md`의 "최우선 새 열린 질문" 절 참고.

View file

@ -123,6 +123,39 @@ GC에 묶이지 않음 — v1이 여기저기서 `PropertyChangedSignal`에 연
저장소), 다른 하나는 "언제까지 실행되어도 되는지"(생명 바인드 + canExecute)를
다룸. 둘 다 base가 제공하는 범용 유틸로 확정.
## 2026-08-04 검증 라운드에서 보강된 내용
**`Connected` 체크는 rbvm 패턴을 그대로 베끼는 게 아니라 base가 인터페이스로만
내보내는 것.** Roblox는 `RBXScriptConnection`에 이미 `Connected`가 존재하고
Destroy 시 모든 커넥션을 즉시 끊어주지만, 다른 엔진에서도 라이프사이클을
확인할 수 있어야 하므로 base는 "이 바인드가 아직 유효한가"를 묻는 람다/인터페이스만
정의하고, quad-roblox가 그 구현을 Roblox의 실제 `Connected`로 채워넣는다(구현
주입 방식은 아래 "base 유틸은 인터페이스, 구현은 백엔드 팩토리" 절 참고). 이게
필요한 이유: rbvm처럼 GC 트릭으로 라이프사이클을 연결하면 GC가 즉발이 아니라서
중간에 죽은 참조가 남아있을 수 있고, 그 시점에 store에 새 값이 들어오면 죽은
대상에 처리를 시도하다 터질 수 있음 — 그래서 처리 직전에 유효성을 확인.
**`Destroying` 훅은 생각보다 덜 중요할 수 있음.** rbvm의 GC-네이티브 무효화
방식(자료구조를 직접 건드리지 않고 네이티브 GC에 후처리를 위임)이 성능상
유리해서, `Destroying` 훅에 명시적으로 의존하는 경로는 실제로는 거의 필요
없을 가능성이 큼 — 확정된 방향(Destroying 하나로 통일)은 유지하되, 실제
구현에서 이 훅을 쓰는 지점이 예상보다 적을 수 있다는 점을 열어둘 것.
**즉시(eager) 정리 예외 두 가지(작고 유계한 포인터, 네임스페이스 dispose)는
quad에는 거의 해당 안 될 가능성이 큼.** rbvm은 이미 존재하는 real DOM 위에
가상 계층을 얹는 구조라 "가상 계층이 필요 없어지면 지운다"는 문제가 있지만,
quad는 자신이 만든 instance를 항상 끝까지 들고 있어서 이런 종류의 즉시 정리
자체가 필요 없을 가능성이 높음 — 실제 구현 단계에서 필요성이 확인되면 그때
추가.
**retract는 Destroy 시점에 필요 없는 이유가 엔진 레벨에서 한 번 더 보강됨.**
Roblox 엔진 자체가 Destroy 시 Tag/Attribute/실행 중인 Tween을 전부 알아서
정리해준다 — 라이브러리가 따로 처리할 필요가 없음. Roblox 이외의 엔진에서
이런 정리가 필요하다면 그건 그 엔진의 `quad-X` 서브패키지가 책임질 문제(base
관심사 아님). 사용자가 커스텀 Destroy-time 처리가 필요하면 `[Event
"Destroying"]`을 직접 바인드해서 처리하면 되는 구조라, 라이브러리가 강제로
제공할 필요도 없음.
## 이름: `cleanup``retract`
"cleanup"이라는 이름은 부적절하다는 사용자 피드백(완전 소멸 정리로 오인되기

View file

@ -1,6 +1,8 @@
# Store 의미론 — 부작용 허용, State 프리미티브 없음
# Store 의미론 — 부작용 허용, State는 Store 위의 조합 가능한 캐시 레이어
**상태**: base — 확정된 설계 결정 두 가지. 원본: `.claude/initreq/raw-userinput.md`
**상태**: base — 부작용 허용/Store 문법 부분은 확정. State/Source 온톨로지는
2026-08-04 검증 라운드에서 새로 열린 진행 중인 설계 스레드(`research/
bind-system-plan.md` 참고). 원본: `.claude/initreq/raw-userinput.md`
"store는 부작용을 허용함" / "state는 어떻게 구현하는가" 절.
## Store는 부작용을 허용하는 게 기본 디자인
@ -14,16 +16,53 @@
"당연히 부작용"이라는 뜻 — 문서화 시 이 경계를 분명히 할 것 (`research/
purity-and-effects-plan.md`와 연결됨).
## 별도 `State` 프리미티브는 만들지 않는다 (기본값)
**보강(2026-08-04 검증 라운드): 부작용은 심각도가 다른 두 갈래로 나뉜다.**
클래스 자신이 필요한 state가 있으면 그냥 클래스 안에서 `Store`를 만들면 됨 —
Store는 부분집합으로 쪼개 전달하는 것도 충분히 가능하다고 보기 때문에, 굳이
"단일 값 저장용" State를 별도로 만들 필요성을 못 느낌. 나누고 싶으면 사용자가
알아서 나누면 됨(사용자 자유).
1. **국소적 부작용** — 입력으로 받았거나 자신이 만들어 소유한 대상에 대한
부작용(예: 렌더 리턴 아래에서 옵저빙해서 자기 slot을 갱신). 이건 편의성이
커서 적극 환영하는 영역.
2. **경계를 넘는 부작용** — globalStore처럼 컴포넌트 바깥의 전역 상태를
다루는 경우. 게임 UI 특성상(스킬/주변 환경에 영향받는 UI 등) 완전히
막을 수는 없지만, 라이브러리로 재사용하려는 컴포넌트가 이런 부작용을
가지면 이식성이 떨어짐(`research/purity-and-effects-plan.md`와 연결).
**단서**: 구현하다가 실제로 State가 있는 게 더 편해지는 지점이 나오면 그때
추가할 수 있음 — 지금은 "필요성이 확인 안 됐다"는 판단이지 "절대 안 만든다"는
확정이 아님. 구현 라운드에서 이 판단이 바뀌면 이 문서를 갱신할 것.
**미해결 열린 질문**: state를 옵저빙해서 나온 결과로 slot에 `clear`/`add` 같은
연산을 할 때, 그 시점에 대상 slot이 이미 죽어있으면 어떻게 되는가 — state가
생성되는 지점과 slot이 적용되는 지점이 서로 다른 스코프라, "state 변경이
발생했을 때 그 slot이 아직 살아있는지"를 어떻게 연관지어 확인할지 아직 명확한
설계가 없음(`isInit=false`일 때는 허용, `isInit=true`이고 생존 확인 함수가
거짓이면 불허 정도의 방향은 있으나 미완성). 사용자 본인도 "더 리서치가 필요"
하다고 명시적으로 표시 — `research/bind-system-plan.md`의 "Store/State/Source
온톨로지" 스레드와 함께 다룰 것.
## 정정(2026-08-04 검증 라운드): `State` 프리미티브는 실제로 필요하다
**이전 버전의 이 절("State 프리미티브는 만들지 않는다")은 틀렸음 — 사용자가
검증 라운드에서 직접 정정.** 정확한 모델:
- **Store는 "source 집합체"이자 state를 만들어주는 존재.** 실제 값이 존재하고
변경될 수 있는 단일 지점은 source(v1의 "값의 근원"에 해당) — store는 이런
source들의 모음.
- **State는 source(또는 다른 state)를 받아 캐싱만 하는 존재, 자기 고유의
독립적 value 개념이 없다.** 단일 값에 대한 state 생성은 store가 자동으로
해주지만, 그 결과를 다시 분기하고 싶으면(하나의 파생 스트림에서 여러
소비자가 각자 다른 추가 compute를 얹고 싶은 경우) `state(state)`처럼 기존
state의 결과를 받아 새 state를 만드는 조합이 필요.
- **store에서 state를 얻는 연산(예: `store "key"`)은 항상 새 state 인스턴스를
반환한다** — state 자체가 캐시되어 재사용되는 게 아니라, source만 store에
귀속된 유일한 실체이고 그 위의 state는 매번 새로 생성됨.
- 이건 quad2-try(폐기된 이전 시도)의 `Pipe` copy-on-write 절충안을 대체하는
방향으로 좁혀짐 — 별도 `Pipe` 타입을 만들어 소유권/버전 가드를 넣는 대신
State 자체가 "파이핑 결합체"이고 `state(state)`로 분기하면 될 걸로 보임
(`Pipe` 후보는 사실상 폐기 쪽으로 기움). 상세는 `research/bind-system-plan.md`
"Store/State/Source 온톨로지" 절 참고 — **아직 완전히 결론난 설계는 아니고,
구현 단계에서 더 다뤄야 할 진행 중인 스레드.**
미해결로 남은 것: `:Compute`의 캐싱/무효화 전략(값이 바뀌었는데 듣는 소비자가
없으면 연산을 미루는 dirty-flag 방식 등), Luau 타입 시스템에서 `store "key"`
같은 커링 호출이 오버로드 함수 타입으로 `state<T>`를 정확히 추론하기 어려운
문제(문자열 리터럴이 as-const로 좁혀지지 않는 문제) — 둘 다 열린 채로
`research/bind-system-plan.md`에서 계속 다룰 것.
## Store 값 설정 문법 — v1 인체공학 유지 (확정)

View file

@ -5,6 +5,45 @@
있음. 사용자가 Lua/Roblox 엔진에 대해 깊이 아는 사람이라는 전제로, 우선순위
높은 것부터 정렬.
## 2026-08-04 검증 라운드 완료
아래 "확정됨" 절 전체(architecture.md 14개 항목, lifecycle-pattern.md,
store-semantics.md, bind-system-plan.md, module-lifecycle-plan.md,
slot-plan.md, tween-plan.md)를 `AskUserQuestion`으로 하나씩 예/아니오 재검증
완료 — 대부분 그대로 확인됐지만, 아래는 검증 과정에서 실제로 문서가 수정된
항목:
- **`State` 프리미티브는 "안 만든다"가 아니라 실제로 필요함** — 정정 완료,
`base/store-semantics.md` 참고. **이 결과로 Store/State/Source 온톨로지
전체가 새로운 열린 설계 스레드로 떠올랐음** — 아래 "최우선 새 열린 질문"
참고.
- Slot의 `retract` 동작이 "부모 위임" 잠정안에서 "폐기(옮기지 않음)"로 확정
`research/slot-plan.md`.
- quad2-try의 `Pipe` copy-on-write 후보는 사실상 폐기, `state(state)` 조합
모델로 대체 — `research/bind-system-plan.md`.
- `Connected` 체크/GC 위임/`Destroying` 훅 관련 뉘앙스 보강(엔진별 인터페이스
주입, quad는 rbvm보다 즉시정리 필요성이 낮음) — `base/lifecycle-pattern.md`.
- base 유틸(per-instance 저장소, 생명 바인드)은 인터페이스만, 실제 구현은
`RobloxFactory(BaseModule)`류 백엔드 팩토리가 주입 — `research/
bind-system-plan.md`.
## 최우선 새 열린 질문 (검증 라운드에서 새로 터져나옴)
- **Store/State/Source 온톨로지 전체** — store는 source 집합체, state는
source를 감싸는 조합 가능한 캐시(자기 고유 value 없음), `state(state)`
분기. `:Compute`의 캐싱/무효화 전략(dirty-flag 등), `emit` 필요 여부, Luau
타입 시스템에서 `store "key"` 커링 호출의 `state<T>` 추론 문제까지 전부
미정 — 다음 세션 최우선 논의 대상. → `research/bind-system-plan.md`
"Store/State/Source 온톨로지" 절.
- **부작용이 slot 생존 여부와 어떻게 연관되는가** — state 옵저빙 결과로
slot을 조작할 때, 그 시점에 대상 slot이 죽어있으면 어떻게 처리할지 사용자도
아직 명확한 답이 없다고 명시. → `base/store-semantics.md`.
- **인스턴스 생성/이벤트 네이밍 인체공학**`Quad "Frame"` 문자열 방식 vs
`DI.Frame` 필드 접근 방식(자동완성/타입추론 트레이드오프). → `research/
bind-system-plan.md`.
- `RobloxFactory` 같은 백엔드 팩토리를 같은 base에 중복 호출했을 때의 가드
동작, 모듈 스코핑(`New()`)과의 관계. → `research/bind-system-plan.md`.
## 확정됨 (2026-08-03 질의응답 라운드, 더 이상 열려있지 않음)
- **Store 책임 분리**: base가 `LifetimeHandle` 추상화 + store-bind의 재실행
@ -50,8 +89,11 @@
- **OOP 상속/`--&` 커스텀 파서/Slot 스텁은 확인대로 죽은 접근** — 절대 반복
금지, Slot은 from-scratch 설계 그대로 진행(재조사 불필요).
- **mutate-vs-`fromState` 긴장 관계**: quad2-try의 `Pipe` copy-on-write
절충안(유일한 tip일 때만 뮤테이션, 아니면 복사)이 유력 후보로 좁혀짐 — 단
소유권/버전 가드를 제대로 설계해야 함(원본은 가드 없이 방치돼 있었음).
절충안(유일한 tip일 때만 뮤테이션, 아니면 복사)이 한때 유력 후보였으나
**2026-08-04 검증 라운드에서 사실상 폐기로 재평가됨** — 별도 `Pipe` 타입
대신 State 자체가 파이핑 결합체이고 `state(state)`로 분기하는 쪽이 더
간단하다는 판단(위 "최우선 새 열린 질문"의 Store/State/Source 온톨로지
절로 흡수됨).
- **`Depend(...)` 액션, `:With` 네이밍**은 이전 시도에서도 지향했던 것과 일치
— 그대로 채택. → `research/bind-system-plan.md`

View file

@ -52,6 +52,13 @@ src/schema/union.luau:48-68`) — 에러 메시지는 즉시 문자열로 만들
그 인스턴스에 적용하도록 돕는 역할에 가깝다. 그래서 핸들러가 "나중에 생길
대상"을 비동기로 기다릴 필요 자체가 없음(아래 Ref 절 참고 — Ref는 다른 이유로
존재).
- **보강(2026-08-04)**: `inst`가 항상 살아있는 엔진 객체(Roblox Instance)일
필요는 없음 — 특정 백엔드에서 실제 엔진 객체 생성/바인딩 비용이 비싸면
(예: 웹 DOM) 중간 표현으로 평범한 테이블을 만들고 나중에 그 테이블을
렌더링하는 것도 가능. 이건 core(base)가 신경 쓸 일이 아니라 각 최종
엔드포인트 백엔드(`quad-roblox`/`quad-web` 등)가 알아서 결정할 문제 —
base 인터페이스는 "무언가를 inst로 받아 process/retract한다"는 계약만
지키면 됨, 그 inst의 실체가 뭔지는 백엔드 재량.
- `process(inst, k, v)` — 우선순위 순으로 등록된 핸들러를 스캔, `isHandlable(k,v)`
만족하는 최상위 핸들러가 실제 처리를 담당.
- 예시: Tween의 store-bind 핸들러는 **`k`는 무엇이든 받고 `v`가 Store인 경우를
@ -181,6 +188,52 @@ read ...`처럼, State끼리 자유롭게 합성/파이핑 가능한 것이 최
같은 축의 문제 — 옵션 2가 그 원칙과 더 잘 맞아 보이지만, 실현 가능성 자체가
아직 검증 안 됨.
## Store/State/Source 온톨로지 — 진행 중인 설계 스레드 (2026-08-04 검증 라운드에서 새로 열림)
**이 절은 아직 결론난 설계가 아니다** — 검증 라운드 중 사용자가 실시간으로
설계를 전개하며 나온 내용을 그대로 기록. 다음 세션에서 이어서 다룰 것.
`base/store-semantics.md`의 "State 프리미티브는 실제로 필요하다" 정정과
직결됨.
**핵심 온톨로지**:
- **Source** — 실제 값이 존재하고 변경될 수 있는 단일 지점(v1의 "값의 근원").
- **Store** — source들의 집합체. `store.a`/`store "a"`처럼 키로 접근하면
그 source를 감싼 **새 State**를 매번 만들어 반환(state가 store에 캐시되어
재사용되는 게 아님 — source만 store에 귀속된 유일한 실체).
- **State** — source(또는 다른 state)의 결과를 캐싱만 하는 존재, 자기 고유의
독립적 value 개념이 없음. `state(state)`로 기존 state의 결과를 받아 새
state를 만들어 분기 가능 — 이게 사실상 Unix 파이프 영감의 "State끼리
합성 가능"이라는 원래 목표를 구현하는 방식.
- `:With(...)`/`:Compute(fn)`은 실제로는 state 위의 연산 —
`store "key1":With(store "key2"):Compute(function(key1) return key1 +
store.key2.value end)`처럼, with한 값을 fn이 클로저로 직접 읽는 모양.
**미해결 세부 사항**:
- **`:Compute` 캐싱/무효화 전략** — 매번 새로 계산할지, 캐싱해두고 무효화
플래그로 관리할지 미정. 후보: 값이 바뀌었는데 듣는 소비자가 없으면 연산은
미루고 `invalid=true`만 세워두고, 필요해질 때(듣는 사람이 생기거나 값을
읽을 때) 실제로 연산하고 `true`→캐싱 후 `false`로 되돌리는 dirty-flag 방식.
"store가 state를 **만드느냐** 아니면 **저장하느냐**"의 문제와 직결 —
사용자 판단은 "만든다" 쪽(저장한다고 하면 한 곳에서 `:Compute`를 붙이면
다른 소비자도 전부 그 compute된 값을 읽게 되어버리는 오염 문제 발생).
- **`emit` 필요 여부** — store가 값 변경 시 관련된 모든 state에 emit해야
하는 구조가 맞는지 확신은 없지만, 그 외의 방법이 안 보인다는 게 사용자
현재 판단. `state(from) / state() -> (state, setState)` 같은 팩토리
모양도 후보로 언급됨(React의 `useState`류 페어 반환과 유사).
- **Luau 타입 시스템 제약**`store "key"` 같은 커링 호출로 `state<T>`
`T`를 정확히 추론하려면 오버로드 함수 타입(`(("a") -> number) | (("b")
-> boolean)`)이 필요한데, Luau는 문자열 리터럴 인자를 자동으로 `as const`
취급하지 않아서 타입이 좁혀지지 않는 문제가 있음. `store.states.a`처럼
필드 접근으로 우회하거나, `store.a`가 바로 state를 반환하고
`state.value = x`로 설정 가능하게 하는 대안도 검토됐으나, 후자는 "다른
source로부터 파생된 state에 value를 직접 설정하면 안 된다"는 문제와
충돌(store가 실제 값을 담는 유일한 주체여야 함). `state<Mapped>`(compute
결과)가 제대로 바인딩 안 됐을 때 생기는 타입 문제는 일단 UB로 두기로 함.
- **`Pipe`(quad2-try 후보)는 사실상 폐기 쪽으로 기움** — 별도 `Pipe` 타입에
소유권/버전 가드를 넣어 재설계하는 대신, State 자체를 파이핑 결합체로
보고 `state(state)`로 분기하는 쪽이 엔지니어링상 더 쉬워 보인다는 게
사용자의 최신 판단(2026-08-03 라운드의 "Pipe COW가 유력 후보"보다 우선함).
## quad2-try 리서치 결과 (완료) — 이전 시도에서 뭘 가져오고 뭘 버릴지
`.claude/initreq/quad2-try/out/quad-core`에 정확히 이 문제(Unix 파이프 영감의
@ -235,9 +288,11 @@ State/스트림)를 다뤘던 이전 시도가 있었음. 조사 결과 요약:
이번 라운드에서 다시 요청한 것과 정확히 일치** — 우연이 아니라 원래
지향점이었던 것으로 보임, `:With` 이름 채택에 힘을 실어줌.
**종합**: 이 프로토타입은 사실상 죽은 시도가 맞음(확인됨) — 다만 Pipe의
copy-on-write 절충안과 `Depend`/`:With` 네이밍은 quad-v2 설계에 그대로
살려볼 가치가 있는 아이디어로 남김.
**종합**: 이 프로토타입은 사실상 죽은 시도가 맞음(확인됨) — `Depend`/`:With`
네이밍은 quad-v2 설계에 그대로 살려볼 가치가 있는 아이디어로 남지만, **Pipe의
copy-on-write 절충안은 2026-08-04 검증 라운드에서 사실상 폐기 쪽으로 재평가됨**
(위 "Store/State/Source 온톨로지" 절 참고 — State 자체가 파이핑 결합체이고
`state(state)`로 분기하는 쪽이 더 간단하다는 사용자의 최신 판단).
## 확정된 것 (더 이상 열린 질문 아님)
@ -249,15 +304,39 @@ copy-on-write 절충안과 `Depend`/`:With` 네이밍은 quad-v2 설계에 그
- **Ref**: 도입 확정(위 절 참고), 용도는 "id 기반 조회 대체"가 아니라 "외부
관리 instance를 점진적으로 다루기 위한 직접 참조 획득".
## base 유틸은 인터페이스, 실제 구현은 백엔드 팩토리가 주입 (2026-08-04 보강)
`base/lifecycle-pattern.md`가 말하는 "범용 유틸"(per-instance 상태 저장소,
생명 바인드 유틸)은 base가 직접 구현하는 게 아니라 **인터페이스만 정의**
`inst`는 base 입장에선 `any`일 수 있음(다른 엔진일 수도 있으므로). 실제
구현은 `RobloxFactory(BaseModule)` 같은 팩토리 함수가 `BaseModule`
뮤테이션해서 그 안에 실 구현체(`canExecute` 등)를 채워넣는 방식 — 사용자는
`quad-base`/`quad-roblox`를 각각 import해서 `const quad =
RobloxFactory(QuadBase)` 세 줄 정도로 직접 조립하면 됨(별도 번들 `quad`
패키지로 재수출할 필요 없음, 필요하면 만들어도 됨).
**열린 질문**: `RobloxFactory`를 같은 `BaseModule`에 여러 번 호출하면 어떻게
되어야 하는가 — 이미 초기화됐으면 무시(rbvm의 `InitNamespace`류 가드와
유사하되, "라이브러리마다 수동 init" 패턴과는 다름)하는 쪽으로 기울어짐.
서로 다른 두 곳에서 같은 `base`를 require해서 `RobloxFactory`
`AnotherFactory`(가상의 예)를 각각 실행하는 경우처럼 충돌 가능성이 있는
시나리오가 향후 모듈 스코핑(`New()`, `base/architecture.md` 13번) 논의를
다시 촉발할 수 있음 — 지금은 열어만 둠.
## 인스턴스 생성 / 이벤트 네이밍 인체공학 (2026-08-04 검증 라운드에서 새로 나온 열린 질문)
`Quad "Frame"`처럼 문자열로 인스턴스 종류를 지정하는 방식은 타입 추론이
어려움(위 온톨로지 절의 Luau 오버로드 문제와 같은 원인). PA님 DI 스타일은
`DI.Frame`/`DI.TextLabel`처럼 필드 접근으로 만들어서 자동완성이 자연스럽게
됨 — 목록에 없는 타입은 `DI.New<<Frame>> "Frame"`류로 폴백. 이벤트도
`Event ""` 대신 `On...`류 이름으로 필드 접근하면 타입/자동완성이 쉬워질 수
있음. 아직 방향 결정 안 됨 — 다음 세션에서 다룰 것.
## 남은 열린 질문 (`.claude/question.md`에도 취합)
- **`:Compute``with`한 값을 정확히 어떻게 읽는가** — 클로저로 원본 store/
register를 직접 캡처하는 것인지, `:Compute`가 특수한 접근자를 몸체 함수에
넘겨주는 것인지 구체 시그니처 미정.
- **mutate-in-place vs `fromState` 긴장 관계** — quad2-try의 copy-on-write
절충안(위 절)이 유력한 후보로 좁혀짐. 실제 구현 시 "내가 유일한 tip인가"
판단에 제대로 된 소유권/버전 가드를 설계하는 게 핵심 과제 — 원본처럼
가드 없이 가면 안 됨.
- **`:Compute``with`한 값을 정확히 어떻게 읽는가** — 클로저로 직접 캡처하는
방향은 확정(위 온톨로지 절), 캐싱/무효화 전략(dirty-flag 등)과 `emit` 필요
여부는 아직 미정.
- **`CreatedRef`(가칭)의 정확한 함수/옵션 이름** — children 배열에 아이템으로
넣는다는 방향과 생성/마운트 두 시점 모두 지원한다는 것은 확정, 정확한 API
이름만 남음.
@ -269,3 +348,5 @@ copy-on-write 절충안과 `Depend`/`:With` 네이밍은 quad-v2 설계에 그
라이프타임도 감싸게 될 텐데, 이중 해제(double-dispose) 방지가 필요한지 확인.
단, `base/lifecycle-pattern.md`의 "destroy 시점엔 아무것도 안 함" 원칙상 이중
해제 자체가 걱정할 필요 없는 개념일 수도 있음 — 재검토 필요.
- `RobloxFactory` 중복 호출/충돌 시나리오, 인스턴스 생성·이벤트 네이밍
인체공학 — 위 두 절 참고, 둘 다 새로 열린 질문.

View file

@ -84,3 +84,13 @@ bind-system-plan.md`의 "확정된 디스패치 모델" 절이 바로 이 base
노출할지 정도(설계 방향 자체는 더 이상 열려있지 않음).
- 넘버 바인드/프로바이더 인터페이스의 정확한 함수 시그니처(base가 요구하는
provider 인터페이스 계약)는 아직 미정 — 구현 착수 시 함께 확정.
- **네이밍 미정(2026-08-04 보강)**: "프로바이더"라고 불러온 개념을 정확히
뭐라고 부를지("provider" vs "processor" vs 그냥 "plug") 아직 안 정함 —
실제로는 `isHandlable`로 받을지 말지 결정하고 우선순위대로 스캔되는
pluggable 참가자라는 점은 확정, 이름만 미정.
- base 유틸(per-instance 상태 저장소, 생명 바인드 유틸)이 인터페이스만 두고
실제 구현은 백엔드 팩토리(`RobloxFactory(BaseModule)`류)가 뮤테이션으로
주입한다는 패턴이 확정됨 — 상세는 `research/bind-system-plan.md`의 "base
유틸은 인터페이스, 실제 구현은 백엔드 팩토리가 주입" 절 참고. 이 패턴을
중복 호출했을 때의 가드 동작(멱등 처리)과 모듈 스코핑(`New()`)의 관계는
여전히 열려있음.

View file

@ -66,6 +66,13 @@ Slot 핸들러 자신이 감시 중인 값(배열/스토어)이 바뀔 때 child
Destroy 시점엔 `retract`가 호출되지 않는다는 원칙(`base/lifecycle-pattern.md`)도
동일하게 적용.
**확정(2026-08-04 검증 라운드): retract되는 slot은 옮겨지지 않고 그냥 폐기된다.**
Slot은 바인딩되는 순간 그 안의 요소를 전부 own해버리는 데이터형 — 새 slot
상태로 교체될 때 이전 slot의 내용을 다른 곳으로 옮기는 경로는 없음, 그냥
버림. React의 portal(`<></>`)류로 나중에 옮길 수 있게 하는 것도 검토됐으나
**이번 마일스톤에서는 오버엔지니어링으로 판단, 하지 않음** — 필요성이 명확해지면
그때 별도로 다시 논의.
## 자식으로 넘기는 클래스 스토어
자식에게 내려주는 클래스 스토어는 부모 쪽에서 미리 만들어서 내려보내는 게
@ -75,7 +82,6 @@ Destroy 시점엔 `retract`가 호출되지 않는다는 원칙(`base/lifecycle-
## 열린 질문 (`.claude/question.md`에도 취합)
- 재마운트 에러 처리는 확정(throw). 남은 건 Slot 안 요소가 `retract`될 때
"부모가 정리 후 재`process`"가 정말 항상 올바른 기본 동작인지, 아니면 slot
자체가 일부 자기 정리를 해야 하는 케이스가 있는지 — 구현하면서 실제 사례로
재검증 필요.
- 재마운트 에러 처리(throw), retract 시 폐기(옮기지 않음) 둘 다 확정. 남은 건
실제 구현 단계에서 이 "폐기" 동작이 실사용에서 불편하지 않은지 재검증하는
정도 — 설계 방향 자체는 더 이상 열려있지 않음.

View file

@ -20,15 +20,21 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
길게 잡음.
**지금은 설계/계획 단계이고 구현은 아직 시작 전** — 저장소 루트에 실제 소스
코드(`src/` 등)가 없음. 2026-08-03 여러 질의응답 라운드를 거쳐 핵심 아키텍처
결정 대부분이 확정됨(Store 책임 분리, `process`/`retract` 디스패치 모델,
Signal 미채택, Ref 역할, Store 문법 인체공학, 트윈 기본 오버라이드, Slot
재마운트 에러 처리, 순수성→이식성 재정의) — `.claude/question.md`의 "확정됨"
절 참고. 이전에 시도했다 폐기한 v2 재작성 시도(`.claude/initreq/quad2-try`)도
리서치 완료 — OOP 상속/커스텀 파서/Slot 스텁은 확인된 죽은 접근이라 반복 금지,
`Pipe`의 copy-on-write 절충안은 살려볼 후보. 남은 건 세부 함수 시그니처
(`:Compute`가 의존값을 읽는 방법, `CreatedRef`/`Store.Combine`류 정확한 이름)
정도 — `.claude/question.md`의 "착수하면서 확인" 절 참고.
코드(`src/` 등)가 없음. 2026-08-03에 확정됐던 핵심 아키텍처 결정들(Store
책임 분리, `process`/`retract` 디스패치 모델, Signal 미채택, Ref 역할, Store
문법 인체공학, 트윈 기본 오버라이드, Slot 재마운트 에러 처리, 순수성→이식성
재정의 등)은 2026-08-04에 `AskUserQuestion`으로 하나씩 재검증까지 마쳐서
확정 상태 — `.claude/question.md`의 "확정됨" 절 참고. 이전에 시도했다 폐기한
v2 재작성 시도(`.claude/initreq/quad2-try`)도 리서치 완료 — OOP 상속/커스텀
파서/Slot 스텁은 확인된 죽은 접근이라 반복 금지, `Pipe`의 copy-on-write
절충안은 한때 살려볼 후보였으나 **2026-08-04에 사실상 폐기로 재평가**됨(State
자체가 `state(state)`로 분기하는 쪽으로 대체).
**지금 유일하게 결론 안 난 핵심 설계 이슈는 Store/State/Source 온톨로지**
— 검증 라운드 중 "State 프리미티브는 안 만든다"던 기존 결정이 틀렸다는 게
드러나며 새로 열림. `.claude/research/bind-system-plan.md`의 "Store/State/
Source 온톨로지" 절, `.claude/question.md`의 "최우선 새 열린 질문" 절 참고 —
아래 "지금 할 일" 1번이 다음 세션이 여기서부터 시작해야 함을 명시.
## 계획 문서 구조
@ -74,35 +80,41 @@ Signal 미채택, Ref 역할, Store 문법 인체공학, 트윈 기본 오버라
## 지금 할 일 (우선순위순)
1. **[다음 세션 최우선] 확정된 설계 검증 라운드.** 2026-08-03에 여러 라운드에
걸쳐 확정한 아키텍처 결정들(`.claude/question.md`의 "확정됨" 절 전체 —
Store 책임 분리, `process`/`retract` 모델, Ref 역할, Store 문법, `:With`/
`:Compute`, 트윈 기본값, Slot 에러 처리, 순수성→이식성 재정의 등)을 **사용자가
명시적으로 요청한 방식으로 재검증할 것**: 각 결정을 작게 쪼개서 예/아니오로
답할 수 있는 질문으로 만들어 `AskUserQuestion`으로 하나씩 확인. 목적은 이
설계 라운드에서 내가(에이전트가) 잘못 이해했거나 성급히 확정한 부분을
찾아내 프로젝트 전반의 기틀과 정확성을 높이는 것 — 이미 답변받은 걸 다시
묻는 게 아니라, "정말 이렇게 이해한 게 맞는지"를 세분화해서 다시 짚는 것.
`.claude/base/`, `.claude/research/` 각 문서를 훑으며 검증 질문 목록을 먼저
만들고, 한 번에 다 던지지 말고 문서/주제 단위로 나눠서 진행할 것.
2. 검증 라운드가 끝나면 `research/bind-system-plan.md`/
1. **[다음 세션 최우선] Store/State/Source 온톨로지 설계.** 2026-08-04 검증
라운드 중 "State 프리미티브는 안 만든다"는 이전 결정이 틀렸다는 게
밝혀지면서 새로 터져나온 핵심 설계 이슈 — Store=source 집합체, State=
source를 감싸는 조합 가능한 캐시(`state(state)`로 분기), `:Compute`
캐싱/무효화 전략, Luau 타입 시스템에서 커링 호출의 `state<T>` 추론 문제
등이 전부 미정. `.claude/research/bind-system-plan.md`의 "Store/State/
Source 온톨로지" 절에 지금까지 나온 내용이 정리되어 있음 — 이어서 설계를
구체화할 것. `.claude/question.md`의 "최우선 새 열린 질문" 절도 함께 참고.
2. 위 온톨로지가 어느 정도 정리되면 `research/bind-system-plan.md`/
`research/module-lifecycle-plan.md``base/`로 승격하고,
`base/architecture.md`에 "구현 착수" 섹션을 추가해 실제 소스 트리 구조
(어느 서브패키지가 뭘 갖는지)를 확정 — 이 시점부터 `qa-request/`/`archive/`
폴더가 실제로 쓰이기 시작함.
3. 남은 세부 시그니처(dependency 값 읽는 방법, `CreatedRef`/`Store.Combine`류
정확한 이름)는 검증 라운드 중 자연스럽게 같이 확정 가능.
4. `research/purity-and-effects-plan.md`, `research/existing-instance-bind-plan.md`
3. 남은 세부 시그니처(`CreatedRef` 정확한 이름, 인스턴스 생성/이벤트 네이밍
인체공학, `RobloxFactory`류 팩토리 중복 호출 가드)는 온톨로지 설계와
자연스럽게 같이 확정 가능.
4. `research/purity-and-effects-plan.md`(특히 "state 옵저빙 결과로 slot을
조작할 때 생존 여부 확인" 열린 질문), `research/existing-instance-bind-plan.md`
급하지 않음 — 스코프 논의만 필요, 구현 착수를 막지 않음.
5. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
(`HUMAN_TODO.md` 2번 항목).
## 인수인계 메모 (2026-08-03 세션 종료 시점)
## 인수인계 메모 (2026-08-04 세션 종료 시점)
이 세션에서 `.claude/` 전체 스캐폴드 + 대부분의 핵심 아키텍처 결정을 완료함.
사용자가 다음 세션 시작 시 이렇게 요청함: "각 디자인 부분을 작게작게 질문으로
만들어서, 이게 맞나요? 예/아니오로 대답할 수 있는 걸 던져가며 검증해보자.
프로젝트의 전반적 기틀 잡힘과 정확성을 올리기를 할 것이라 해둬." — 위 "지금
할 일" 1번이 이 요청을 그대로 반영한 것. 로컬 git 저장소는 이 세션에서 초기화
+ 첫 커밋까지 해둠(원격 없음, `SAFETY.md` 참고 — 원격은 사용자가 제한 계정을
마련해줘야 추가 가능).
2026-08-03에 확정됐다고 표시된 결정 전체(architecture.md 14개 + lifecycle-
pattern/store-semantics/bind-system-plan/module-lifecycle-plan/slot-plan/
tween-plan)를 `AskUserQuestion`으로 하나씩 예/아니오 검증 완료 — 상세는
`.claude/question.md`의 "2026-08-04 검증 라운드 완료" 절. 대부분 그대로
확인됐지만, 검증 과정에서 사용자가 실시간으로 설계를 더 전개하면서 **"State
프리미티브는 안 만든다"는 기존 결정이 틀렸다는 게 밝혀짐** — Store/State/
Source 온톨로지 전체가 이번 세션에서 새로 열린 가장 중요한 설계 스레드로
떠올랐고, 아직 결론이 안 났음(위 "지금 할 일" 1번). 그 외 자잘한 정정들(Slot
retract=폐기 확정, Pipe COW 후보 폐기 등)은 각 문서에 바로 반영해둠 — 재조사
불필요.
이전 세션(2026-08-03) 종료 시점 메모: `.claude/` 전체 스캐폴드 + 대부분의
핵심 아키텍처 결정을 완료, 로컬 git 저장소 초기화+첫 커밋(원격 없음,
`SAFETY.md` 참고 — 원격은 사용자가 제한 계정을 마련해줘야 추가 가능).