decide(state,operator,ref): use-after-destroy/동적With 의도적 비지원 확정, Operator 카탈로그 외부 리서치 반영, State 이름 최종 확정

- use-after-destroy 검증 안전망: rbvm 영역/quad-debug 스코프 밖으로 최종
  기각, Ref 사용 관례(useRef급 스코프) 명문화
- :With 동적 의존성: State immutable 가정과 모순되어 의도적 비지원 확정
- Operator 콤비네이터 카탈로그: 서브 에이전트 외부 리서치로 포함 범위/
  네임스페이스 이름 근거 보강(Clamp/Min/Max 추가 후보, 비트/비교/Sub/Div
  드랍 후보, Debounce/Throttle 별도 질문으로 분리)
- State 용어 정리 최종 확정(현재 이름 유지), Pipe 기각 근거·Compute vs
  Computed 네이밍 근거 문서화, :With와 Tag/Modifier clone 체이닝 혼동
  방지 경고 추가

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
This commit is contained in:
qwreey 2026-08-12 21:14:15 +09:00
parent 4c5ee1b66a
commit 2688409ebd
Signed by: qwreey
GPG key ID: D28DB79297A214BD
9 changed files with 422 additions and 36 deletions

View file

@ -894,6 +894,15 @@ tween-plan.md`도 이에 맞춰 갱신됨). Ref의 진짜 용도는 다름:
- Store는 이미 바깥에서 옵저빙 가능한 존재라 별도 취급 불필요 — Ref는 그와
달리 "원하는 객체 자체를 직접 얻어오는" 경로. **얻어진 뒤에 그 참조를 어디에
저장하고 어떻게 쓰는지는 라이브러리 책임 범위 밖**(사용자 자유).
**권장 관례(2026-08-12, use-after-destroy 검토에서 명문화):** Ref는
이를 만든 컴포넌트 자신이 쓰거나 자식에게 넘겨 쓰는 용도가 관례 —
React `useRef`와 같은 스코프 감각. 컴포넌트 경계를 넘어 위로
반출하거나 전역에 장기 보관하는 건 권장하지 않음 — Ref는 Destroy와
완전히 무관하게 동작하므로(아래 "Destroy와는 무관" 절), 관례를 벗어난
반출·장기보관은 use-after-destroy가 발생할 수 있는 사실상 유일한
자리가 됨. quad는 이 케이스에 런타임 안전망을 두지 않기로 확정
(`research/framework-comparison-findings.md` 3번 절 근거) — 대응은
이 관례를 지키는 것뿐, 위반 시 결과는 완전한 UB.
- **바인드 방법**: children을 배열 아이템으로 넣듯 `Ref(default)`(또는
`:Callback(fn)`을 미리 걸어둔 `Ref(default):Callback(fn)`) 인스턴스
자체를 숫자 키 슬롯에 그대로 넣는 방식 — `(v=Ref)` 매치 핸들러가 이걸
@ -1490,6 +1499,25 @@ lazy State 핸들로 통일, 아래 "Store/State/Source 온톨로지" 절의 "`:
`:Compute`가 원래부터 이 셋 중 제일 먼저 있던 자리라 뒤늦게 문서화된
것뿐, 새 결정이라기보다 이미 있던 패턴을 명문화한 것.
### 네이밍 — `Compute``-ed`가 아닌 이유 (2026-08-12, `State` 용어 정리 라운드 후속)
`Tag``Added`/`Removed`, `Modifier``Overridden`은 전부 `-ed`(과거분사)
어미를 의도적으로 씀 — `tag-plan.md`가 밝힌 이유는 "`Add`/`Remove`로 쓰면
뮤테이션 API처럼 보이기 때문"(실제로는 항상 clone 후 즉시 확정된 새 값을
반환). **`:Compute`/`:With`는 정반대 이유로 이 관례를 의도적으로 안 따름.**
Tag/Modifier의 클론은 호출 즉시 결과가 확정되는 값이라 "-ed"(이미 끝난
일)가 정확한 묘사지만, `:Compute(fn)`이 만드는 State 노드는 **호출 시점엔
`fn`을 등록만 해둔 것뿐이고 실제 계산은 나중에 `:Get()`이 pull할 때
일어남**(push-invalidate/pull-recompute 모델, 아래 "Store/State/Source
온톨로지" 절) — 즉 호출 시점에 "computed"(이미 계산됨)라고 부르면 거짓.
`State``Computed`로 리네임하는 안이 최종 기각된 것(`question.md` 1번)도
같은 이유의 연장 — Vue `computed()`/Svelte `$derived`가 lazy인데도 그
이름을 쓰는 건 그쪽 생태계에서 문제없지만, quad 자신의 코퍼스 안에서는
"-ed 어미 = 이미 즉시 확정된 값"이라는 관례가 Tag/Modifier로 이미 자리
잡아서, 같은 어미를 lazy한 것에 재사용하면 quad 자기 관례와 충돌해 오히려
더 헷갈림. 그래서 `Compute`(동사 원형, "계산을 등록/설정한다"는 뜻)가
`Computed`보다 quad의 명명 체계 안에서 정확함.
### `:Compute(fn, ...)` — 추가 의존성을 trailing args로 직접 받는 sugar (2026-08-11)
**문제 제기(사용자)**: React의 `useMemo(fn, deps)`처럼 `:With(...)` 없이
@ -2159,6 +2187,16 @@ Modifier처럼 플래튼하지 않는가"는 설계 근거를 알고 싶은 사
노드는 Observer와 같은 패턴(외부 weak table)으로 상위 노드의 구독자 목록에
등록됨.
**⚠️ 문서 읽을 때 혼동 주의(2026-08-12 추가, 코퍼스 전체에 같은 패턴으로
적용): `Tag`(`:Added`/`:Removed`)와 `Modifier`(`:Apply` 등)는 겉보기엔
같은 `:` 체이닝 문법이지만 실제로는 clone-then-return이고, State의
`:With`/`:Compute`는 이름은 비슷해 보여도 정반대(clone이 아니라 진짜 새
노드)임.** 하나가 clone 계열, 다른 하나가 새-노드 계열이라는 걸 헷갈리기
쉬우니(둘 다 "값을 안 바꾸고 새 걸 반환하는 메소드 체이닝"으로 보이기
때문) 각 API 문서를 볼 때 이 문단을 기준으로 확인할 것 — clone 계열은
`Tag`/`Modifier`(값 객체, 확정 상태), 새-노드 계열은 `State`
`:With`/`:Compute`(반응형, lazy)로 완전히 분리되어 있고 섞이지 않음.
**노드 증식 걱정은 가변인자로 해소.** 처음 문제 제기("With 하나마다 노드가
하나씩 늘어나는 게 낭비 아니냐")는 노드 자체를 없애는 대신, `:With(...)`
여러 의존성을 한 번에 받을 수 있게 해서 해소함:

View file

@ -54,12 +54,25 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
볼게." 1차 제안 완료, 아래는 우선순위순 요약 — 최종 판단은 사용자와 계속
논의 필요:
- **`State`(1순위, 위험도 높음)**: 지금 정의는 "읽기 전용, 파생/캐시 뷰"인데
React/Vue 등 업계 전반에서 "state"는 거의 항상 "쓸 수 있는 로컬 슬롯"을
뜻함 — 처음 보는 사람이 정반대로 오해할 위험이 큼. `Computed`/`Derived`
(Vue `computed()`, Svelte 5 `$derived`가 정확히 같은 의미로 씀)가 실제
의미에 더 맞아 보임. 단, v1의 "register"를 이미 한 번 "State"로 리네임한
지 얼마 안 됐다는 점 고려 필요.
- **[해소됨, 2026-08-12 세션, 같은 날 후속 세션에서 근거 보강]** `State`
사용자가 현재 이름 그대로 유지로 확정("그걸로 충분한듯"). 검토했던
대안과 기각 근거:
- `Computed`/`Derived`(Vue `computed()`, Svelte `$derived`) — 리네임
안 함. 후속 논의로 근거가 하나 더 붙음: quad 코퍼스 안에서 `-ed` 어미는
이미 `Tag.Added`/`Removed`, `Modifier.Overridden`이 "clone 후 즉시
확정된 값"이라는 뜻으로 선점해둔 관례(`tag-plan.md` 참고)라, `State`
노드가 실제로는 lazy(`fn`을 등록만 해두고 `:Get()`이 pull할 때
계산됨)인데 `Computed`라는 이름을 쓰면 quad 자기 관례와 충돌해 "이미
계산 끝난 값"으로 오해하기 쉬움 — Vue/Svelte 생태계에서는 lazy와
`computed`라는 이름이 공존해도 문제없지만, quad 안에서는 다름. 같은
이유로 `:Compute`(동사 원형) 메소드 이름도 `Computed`가 아니라
`Compute`인 게 맞다고 재확인(`base/bind-system-plan.md` "네이밍 —
`Compute``-ed`가 아닌 이유" 절).
- `Pipe` — 검토했으나 기각. (1) "캐시한다"는 동작이 파이프라는 비유와
안 맞음(파이프는 통과시키는 채널 이미지라 값을 들고 있다/캐시한다는
느낌이 잘 안 붙음), (2) 파이프는 흐름/연결의 이미지라 State가 실제로는
각각 주소를 가진 독립된 그래프 노드 단위라는 것과 안 맞음(단위를
"노드"로 보기 애매해짐).
- **`DI`(Declarative Instance, 1순위)**: "Dependency Injection"의 업계
표준 축약어와 완전히 겹침 — 4차 라운드에서 이미 한 번 실제로 오해가
있었던 전례(`base/bind-system-plan.md`의 "인스턴스 생성" 절 참고).
@ -216,12 +229,19 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
### 3. 낮은 우선순위
- **`Operator` 콤비네이터 슈가 네임스페이스 이름(2026-08-12 신설)** —
`Sum`/`Product`/`Not`/비트연산 등 `:Compute`/`:Apply`용 슈가 함수 모음의
이름. 흔한 단어라 top-level 노출은 위험, 후보는 `Operator`/`Op`/`Ops`
(`Combinator`는 코퍼스 전반에서 이미 일반명사로 쓰여서 제외) — 아직
미정. `research/operator-sugar-plan.md` 참고. 구현 자체는 맨 마지막
우선순위(순수 슈가, 없어도 무방).
- **`Operator` 콤비네이터 슈가 네임스페이스 이름+포함 범위(2026-08-12 신설,
같은 날 후속으로 외부 리서치 완료)** — `Sum`/`Product`/`Not`/비트연산 등
`:Compute`/`:Apply`용 슈가 함수 모음의 이름. 흔한 단어라 top-level
노출은 위험, 후보는 `Operator`/`Op`/`Ops`(`Combinator`는 코퍼스 전반에서
이미 일반명사로 쓰여서 제외) — **서브 에이전트 외부 리서치 결과 `Operator`
가장 선례가 강함**(Python `operator` 모듈)이나 최종 확정은 여전히 사용자
몫. 같은 리서치에서 포함 범위도 새로 갈렸음 — 비트/비교 연산자 그룹과
`Sub`/`Div`는 리액티브 콤비네이터로서 선례가 전혀 없어 드랍 후보로,
`Clamp`/`Min`/`Max`는 선례가 강해 추가 후보로, Debounce/Throttle은
업계에 흔하지만 `Blocker`와는 다른 시간 기반 메커니즘이라 이 카탈로그가
아니라 quad-roblox 쪽 별도 프리미티브로 다룰지 판단이 필요한 별개 질문으로
분리됨. 상세는 `research/operator-sugar-plan.md`. 구현 자체는 맨 마지막
우선순위(순수 슈가, 없어도 무방) — 여전함.
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
착수를 막지 않음.
- **v1 `objectListClass.__newIndex` 오타 기능의 재현 테스트 필요**
@ -270,11 +290,15 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
`registerClass` 체이닝 기능 브릿징 필요성)은 문서 자체가 "지금 결정
불필요"로 표시해둠 — 위 Slot 항목과 별도로, 실제 compat 레이어 구현
시점에 `research/v1-compat-plan.md` §8을 다시 열어 확인.
- **`framework-comparison-findings.md`의 두 남은 개선 후보 반영 여부** —
`research/framework-comparison-findings.md` "다음 단계" 절. use-after-destroy
검증 안전망 부재, `:With`의 정적 의존성(동적 With 미지원) 두 가지를 실제
설계에 반영할지, 반영한다면 M0 스파이크 때 같이 검증할지 나중 최적화
패스로 미룰지 — 아직 사용자 판단 전.
- **[해소됨, 2026-08-12 열여덟 번째 세션]** `framework-comparison-findings.md`
두 남은 개선 후보 — 둘 다 "고칠 필요 없음, 의도된 설계"로 사용자가 최종
판단해 문서 3번 절(못 고치는 트레이드오프)로 이전. use-after-destroy 검증
안전망은 `bindLifetime`/`Effect`로 이미 커버되는 영역에 별도 장치를 얹는
게 오히려 GC-native 아키텍처(수동 Destroy 강제 없이 GC가 치우게 두는 것)와
모순되어 완전한 UB로 남기고 문서화로만 대응. `:With`의 동적 의존성도
State immutable 가정과 정면 모순(실사용 사례도 거의 없음, React
`useMemo` deps도 대부분 정적) — 의도적 비지원으로 확정. 상세는
`research/framework-comparison-findings.md` 3번 절.
## 참고: 지금까지 확정된 것 (요약)

View file

@ -44,18 +44,6 @@
## 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를 만족함" 절) —
@ -64,6 +52,46 @@
## 3. quad가 불리한 점 중 — 못 고치는 것(의도된 트레이드오프, "고친다" 개념 자체가 안 맞음)
- **[2026-08-12 열여덟 번째 세션, 사용자 최종 판단 — 원래 2번(fixable)에
있었으나 여기로 이전. 같은 날 후속 세션(스무 번째)에서 근거 보강]**
use-after-destroy 검증 안전망 부재. Fusion `Memory/checkLifetime.luau`
사전 검증을 quad가 일반적으로 흡수하는 건 애초에 실행 불가능에 가까움 —
제대로 하려면 (a) 등록된 모든 함수/클로저를 추적해 `inst` 사용을
전부 조사하거나 (b) `inst` 자체를 래핑해 이후의 모든 읽기/쓰기를
가로채야 하는데, 이건 quad가 손댈 수 있는 범위를 벗어나는 Instance
가상화/추적 문제 — 정확히 이 목적으로 존재하는 rbvm 같은 전문
Instance-래퍼 라이브러리의 영역. quad가 이걸 재발명하면 그 자체로
중복·오버엔지니어링이고, 이 수준의 디버깅이 필요하면 quad-debug가
rbvm 같은 도구를 병행하도록 안내하는 게 맞는 방향(quad 혼자서 모든
`inst` 사용을 추적하는 건 못 함).
또한 **quad-debug 자신의 스코프도 이 문제와 안 겹침** — quad-debug는
quad-base/quad-roblox가 스스로 만들어낸 효과(Store/Dispatch/handler가
뭘 왜 세팅했는지)를 설명하는 도구이고, `research/debug-tooling-plan.md`
"외부 변경 감지" 절이 이미 "Ref로 얻은 raw Instance에 대한 직접 조작은
애초에 `process()`를 거치지 않아 quad-debug의 계측 지점으로 절대 안
잡힌다"고 명시해뒀음 — 외부에서 뭘 하는지는 원래부터 관심사가 아님.
실제로 use-after-destroy가 발생할 수 있는 자리는 딱 하나로 좁혀짐 —
quad가 케어 안 하기로 이미 확정한 `Ref`가 의도된 스코프를 벗어나
외부로 반출/장기 보관되는 경우. 이건 이미 권장하지 않는 사용 패턴이고,
Ref의 관례(React `useRef`와 동일한 수준 — 만든 컴포넌트 자신이 쓰거나
자식에게 넘겨 쓰는 용도, 경계를 넘어 반출/전역 보관하지 않음)를
`base/bind-system-plan.md`에 명시적으로 문서화하는 것으로 충분 —
런타임 추적이 아니라 관례 문서화가 맞는 대응. `Tag`/`Attribute`/`Tween`
같이 quad가 실제로 소유·관리하는 요소에 대해서는 더 자세한 전용
디버깅 유틸(quad-debug 백로그)을 제공할 수 있고 그게 투자 대비 가치가
더 큼 — 모든 것에 `Destroying`을 Connect해 범용 안전망을 만드는 방향은
기각. 별도 검증 레이어(옵트인이든 아니든) 계획 없음.
- **[2026-08-12 열여덟 번째 세션, 사용자 최종 판단 — 원래 2번(fixable)에
있었으나 여기로 이전]** `:With(...)` 정적 의존성(동적 재평가 미지원).
동적 의존성 목록을 지원한다는 것 자체가 "State는 immutable하다"는 quad
전역 가정과 모순 — 의존성 목록이 실행 중 바뀔 수 있다면 그 변경이 후행
노드 전체에 파급되는 걸 허용해야 하는데, 이건 quad가 계속 원치 않아온
것. 실사용 사례도 사실상 없음 — React의 `useMemo(fn, [...])`도 실무에서
deps 배열을 동적으로 조립하는 경우가 거의 없고(그러면 그 훅이 거의 항상
실행돼버려 메모이제이션 의미가 없어짐), 대부분 정적으로 나열함. 억지로
끼워넣을 이유가 없는 의도적 비지원 요소로 확정. (State가 아닌 다른
방법으로 유사한 걸 지원할 수 있는지 자체는 나중에 별도로 리서치해볼 수
있으나, 이미 결론난 현재 State 설계에 손을 대는 방향은 아님.)
- **암묵적 추적의 인체공학적 우위(vs Vide)**`derive()` 안에서 그냥
호출하면 의존성이 잡히는 Vide 대비, quad는 전부 `:With`에 나열해야 해
보일러플레이트가 늘어남. quad가 "Lua에서 암묵 추적은 부작용 관찰이
@ -101,10 +129,11 @@ React 커스텀 훅만큼의 합성성을 실사용 규모에서 주는가"도
## 다음 단계
이 문서 자체는 지금 당장 뭘 바꾸라는 결정문이 아님 — 사용자가 직접 검토
후 판단할 항목:
- 2번의 남은 두 가지(use-after-destroy 검증, 동적 With)를 실제로 설계에
반영할지, 반영한다면 언제(M0 스파이크 때 같이 검증할지, 나중 최적화
패스로 미룰지). 세 번째(Store dot-access 할당)는 위에서 이미 해소됨.
**[2026-08-12 열여덟 번째 세션 — 해소됨]** 2번(fixable)에 남아있던 두 항목
모두 "고칠 필요 없음"으로 사용자가 최종 판단 — 3번(의도된 트레이드오프)으로
이전 완료, 상세 근거는 3번 절 참고. 2번은 이제 전부 해소된 항목만 남음, 이
문서 자체는 더 이상 사용자 판단 대기 상태가 아님.
- 1번 강점 목록은 `research/documentation-content-map.md`의 "왜 quad를
쓰는가" 초심자/quadnomicon 콘텐츠 소재로 재사용 가능.
쓰는가" 초심자/quadnomicon 콘텐츠 소재로 재사용 가능(유일하게 남은 재활용
대상).

View file

@ -176,16 +176,51 @@ introspection 로직을 추가하는 거라 라이브러리 복잡도가 늘어
(`question.md` 1번, `Brand`/`Tag`류)와 같은 카테고리로 나중에 같이
검토해도 됨 — 급하지 않음.
**[2026-08-12 추가]** 서브 에이전트 외부 리서치(아래 "외부 리서치 결과"
절) 결과, `Operator`가 가장 강한 실제 선례를 가짐 — Python 표준 라이브러리
`operator` 모듈(`operator.add`/`operator.and_`/`operator.lt` 등)이 quad의
정확히 같은 동기(연산자를 `map`/`reduce` 등에 넘길 수 있는 이름 붙은
함수로 만드는 것, quad에선 `:Apply`가 그 자리)로 존재하는 직접 선례.
`Ops`는 Rust `std::ops`가 근거이나 그건 연산자 오버로딩용 trait 네임스페이스라
"콤비네이터 함수 모음"이라는 quad의 용도와는 결이 다름(약한 선례). `Op`
(단수)는 오히려 Slate.js `Op`/Immer patch처럼 "낱개 연산 객체 하나"를
가리키는 데 더 흔히 쓰여 네임스페이스 이름으로는 가장 약한 후보 — 최종
결정은 여전히 사용자 몫이지만, 후보 중 고르라면 `Operator`가 근거가 가장
탄탄함.
## 열린 질문 — 포함 범위
- 산술(`Sum`/`Product`/`Sub`/`Div`?)·논리(`Not`/`And`/`Or`/`Xor`)·비트
(`Band`/`Bor`/`Bxor`/`Bnot`/`Shl`/`Shr`)까지는 비교적 명확한데, 비교
연산자(`Eq`/`Lt`/`Gt`/`Lte`/`Gte`)까지 포함할지는 미정 — 포함해도 같은
패턴(커링 팩토리 + `:Apply`)으로 자연스럽게 들어감.
패턴(커링 팩토리 + `:Apply`)으로 자연스럽게 들어감. **[2026-08-12 외부
리서치로 갱신, 아래 절 참고]** 비트/비교 그룹은 "리액티브 파생값
콤비네이터"로서의 실제 선례가 전혀 없는 것으로 확인됨(양쪽 다 다른
라이브러리에서 인라인 연산자로만 쓰임) — 포함하더라도 업계 관행을 따르는
게 아니라 quad가 처음 시도하는 조합이라는 점을 인지하고 판단할 것.
`Sub`/`Div`도 명명된 콤비네이터로서의 선례가 전혀 없어(어디서든 인라인
`-`/`/`만 씀) 드랍 후보. `Xor`도 VueUse가 `And`/`Or`/`Not`은 다 갖췄으면서
의도적으로 뺀 것으로 보여 약한 후보.
- `Sum(a, b, ...)`가 self까지 포함해서 더하는 형태(위 예시)로 확정 —
사용자 원 예시(`:Apply(Sum(state, state...))`)와 일치. self 없이 여러
state를 독립적으로 합치는 형태가 따로 필요한지는 실사용 사례가 나오면
재검토(지금은 `Store.Combine`류로 이미 커버된다고 봄).
- **[2026-08-12 외부 리서치로 신설]** `Clamp`/`Min`/`Max` — VueUse
`useClamp`/`useMax`/`useMin`, Ramda `R.clamp` 등 리액티브 파생값
콤비네이터로서의 선례가 뚜렷함. 기존 "산술" 뭉치보다 오히려 이쪽이
선례가 강해 별도 그룹으로 추가할 후보.
- **[2026-08-12 외부 리서치로 신설, 별도 검토 필요 — Operator 카탈로그와
성격이 다름]** Debounce/Throttle — RxJS `debounceTime`/`throttleTime`,
VueUse `useDebounce`/`useThrottle` 등 업계 전반에서 가장 흔한 리액티브
콤비네이터 카테고리 중 하나라 부재가 눈에 띔. 단, **quad의 `Blocker`
(`base/blocker-plan.md`)와는 다른 메커니즘** — `Blocker`는 유저 코드가
직접 `:On()`/`:Off()`로 여닫는 값 기반 게이트(타이머 없음)라 시간 기반
지연/합치기가 아님. 실제 debounce/throttle을 만들려면 타이머(엔진 종속,
Roblox `task.delay` 등)가 필요해 `factory(self) -> State<U>` 순수 함수
모양을 벗어남 — `Operator.*`(quad-base, 엔진 무종속)에 넣을 수 있는 게
아니라 quad-roblox 쪽 별도 프리미티브(Tween과 비슷한 위치)로 다뤄야 할
가능성이 큼. 이 문서 범위 밖의 별도 설계 질문으로 분리해서 판단 필요 —
지금 착수 안 함, 사용자 판단 대기.
## 열린 질문 — 컬렉션 계열 후보: `Concat`/`Sorted`/`Filtered`
@ -208,6 +243,19 @@ introspection 로직을 추가하는 거라 라이브러리 복잡도가 늘어
슈가가 아니라 Slot 쪽 조정 로직과 맞물리는 별도 메커니즘이 필요할 가능성이
큼. Slot을 거치지 않는 순수 `State<table> -> State<table>` 값 변환
용도라면 문제없이 들어갈 수 있음 — 실사용 사례가 나오면 재검토.
**[2026-08-12 외부 리서치로 이 판단이 실제 선례로 뒷받침됨]** ReactiveUI의
`IReactiveDerivedList`/`CreateDerivedCollection`은 필터링을 "일반 파생값"이
아니라 아예 별도의, 변경분만 증분 반영하는 전용 컬렉션 타입으로 다룸 —
즉 quad의 `Slot`과 같은 결의 "더 무거운 전용 프리미티브가 필요하다"는
판단과 정확히 같은 결론. 반대로 SolidJS의 `createMemo(() => items.filter(...))`
`<For>`에 바로 먹이는 흔한 패턴은 문제없이 동작하는데, 이건 memo
자신이 아니라 `<For>`가 내부적으로 keyed reconciliation을 따로 하기
때문 — memo의 identity 처리와 무관하게 하위 컴포넌트가 스스로
재조정한다는 뜻. 이 대조가 quad의 결론을 그대로 뒷받침: **원소 identity
보존이 필요 없는 자리(Slot을 안 거치는 순수 값 변환)라면 `Filtered`
plain value transform으로 둬도 되지만, identity를 보존해야 하는 자리는
이미 `Slot`이라는 별도 무거운 프리미티브가 담당해야 하는 영역** — 어중간한
타협이 아니라 정확한 경계선.
## 열린 질문 — Attribute 그룹 명시적 unset 유틸 (2026-08-12 세션 후속, 신설)

View file

@ -0,0 +1,49 @@
# 2026-08-12 열여덟 번째 세션 — `framework-comparison-findings.md` 남은 두 항목 "고칠 필요 없음"으로 최종 판단
**배경**: `question.md` 낮은 우선순위 목록에 남아있던
`framework-comparison-findings.md`의 "고칠 만한 것" 2번 절 두 항목
(use-after-destroy 검증 안전망 부재, `:With`의 정적 의존성/동적 With
미지원)이 여전히 "사용자 판단 전" 상태였음. 사용자가 이번 세션에서 둘 다
명확한 근거를 들어 "고칠 필요 없음, 의도된 설계"로 확정.
## use-after-destroy 검증 안전망
사용자 논지: quad는 `bindLifetime`으로 대부분의 연산을 GC-native하게
지원하도록 이미 설계돼 있고, 말단 leaf에 직접 연결하지 않는 이상 Instance를
직접 관리하는 건 quad가 처리하는 영역이 아님. 만일을 위한 부작용 정리
수단으로 `Effect`도 이미 제공됨. Destroy 시 정리해야 할 게 있다면, 그
부작용을 만들어낸 코드가 처리할 책임을 짐(Ref의 "Destroy 무관" 철학과
동일선상). 만약 quad가 이 영역에서 에러를 내주는 방향으로 간다면, 그건
언제나 명시적 `Destroy()` 호출을 강제하게 되어버려 "가볍게 GC가 알아서
치울 때 같이 처리되도록 놔두고 네이티브가 처리 성능을 올려주는" 아키텍처
전체와 모순됨. 완전한 UB 영역으로 두는 게 정확한 아키텍처 — quad가 할 수
있는 유일한 대응은 유저가 그런 행동을 안 하도록 문서화로 돕는 것뿐.
## `:With`의 동적 의존성(동적 With 미지원)
사용자 논지: 동적 의존성을 지원한다는 것 자체가 "State는 immutable하다"는
현재 가정을 깨뜨림 — 의존성 목록이 바뀔 수 있다면 그 변경이 후행하는 다른
노드들 전체에 영향을 미치는 걸 허용해야 하는데, 이건 quad가 계속 원치
않아온 것. 애초에 동적 의존성이라는 것 자체가 사용 사례가 사실상 없음 —
React를 봐도 `useMemo(fn, [...])`의 deps 리스트를 동적으로 조립하는 경우는
거의 없고(그러면 그 훅이 거의 항상 실행돼버려 메모이제이션 의미가 없어짐),
보통 정적으로 나열함. 억지로 끼워넣는 건 무리 — 의도적 비지원 요소로 확정.
(State 이외의 다른 방법으로 유사한 걸 지원할 수 있는지 리서치는 나중에
해볼 수 있으나, 이미 결론에 다다른 현재 State 설계에 손을 대는 방향은
아님.)
## 반영
- `research/framework-comparison-findings.md`: 2번 절(고칠 만한 것)에서
두 항목 제거, 3번 절(못 고치는 것 — 의도된 트레이드오프)로 근거와 함께
이전. "다음 단계" 절도 "해소됨"으로 갱신 — 이 문서는 더 이상 사용자
판단 대기 항목이 없음.
- `.claude/question.md`: 낮은 우선순위 목록의 해당 항목을 `[해소됨]`으로
표시.
## 부수 확인 (문서 반영 불필요, 현황 재확인만)
사용자가 같은 메시지에서 `quad-v1-compat`/`quad-debug`는 여전히
`quad-base`/`quad-roblox` 완성 전에 착수 가능해 보이지 않는다고 재확인 —
기존 `CLAUDE.md`/`question.md`가 이미 못박아둔 우선순위와 정확히 일치,
변경 없음.

View file

@ -0,0 +1,45 @@
# 2026-08-12 열아홉 번째 세션 — `Operator` 콤비네이터 슈가 외부 리서치
**배경**: 사용자 요청으로 서브 에이전트(general-purpose, 웹 리서치 전용)를
띄워 `research/operator-sugar-plan.md`가 이미 스케치해둔 `Operator.*` 카탈로그
(Sum/Product/Not/And/Or/Xor/비트연산/비교연산/Concat/Sorted/Filtered)를
다른 언어/리액티브 라이브러리 실제 선례와 대조. 목적은 결정이 아니라
카탈로그 포함 범위와 네임스페이스 이름 논의에 근거를 보강하는 것.
## 리서치 결과 요약
- **선례 있음**: 논리(`Not`/`And`/`Or`) — VueUse `@vueuse/math`
`logicAnd`/`logicOr`/`logicNot`이 정확히 같은 모양. `Sum` — VueUse
`useSum`. `Clamp`/`Min`/`Max` — VueUse `useClamp`/`useMax`/`useMin`,
Ramda `R.clamp`(quad 카탈로그에 없던 새 후보로 추가 가치).
- **선례 없음/약함**: 비트연산·비교연산자는 어떤 리액티브 프레임워크에서도
"이름 붙은 파생값 콤비네이터"로 존재한 적이 없음(전부 인라인 연산자로만
씀) — 포함하면 업계 관행이 아니라 quad가 처음 시도하는 조합. `Sub`/`Div`도
마찬가지로 선례 전혀 없음(드랍 후보). `Xor`도 VueUse가 나머지 셋은 다
갖췄으면서 의도적으로 뺀 걸로 보여 약한 후보.
- **누락 발견**: Debounce/Throttle이 업계에서 가장 흔한 리액티브 콤비네이터
카테고리인데 quad 스케치에 없음 — 그런데 quad의 `Blocker`는 유저가 직접
여닫는 값 기반 게이트(타이머 없음)라 다른 메커니즘. 실제 시간 기반
debounce/throttle은 타이머(엔진 종속)가 필요해 `factory(self)->State<U>`
순수 함수 모양을 벗어나므로, `Operator.*`(quad-base)가 아니라
quad-roblox 쪽 별도 프리미티브(Tween과 비슷한 위치)일 가능성이 큼 —
이 문서 범위 밖 별도 질문으로 분리.
- **`Filtered` 판단 뒷받침**: ReactiveUI `IReactiveDerivedList`가 필터링을
아예 별도의 증분 갱신 전용 컬렉션 타입으로 다루는 것이 quad `Slot`
같은 결 — SolidJS `createMemo`+`filter`를 `<For>`에 먹이는 패턴이
문제없이 동작하는 건 `<For>` 자신이 keyed reconciliation을 하기
때문(memo identity 처리와 무관). "identity 보존 필요 없으면 plain
value transform, 필요하면 Slot"이라는 quad의 기존 경계가 정확했음을
확인.
- **네임스페이스 이름**: Python 표준 라이브러리 `operator` 모듈이 가장
강한 직접 선례(`operator.add`/`operator.lt` 등, quad와 동일한 동기 —
연산자를 `map`/`reduce`류에 넘길 이름 붙은 함수로 만드는 것). `Ops`
Rust `std::ops`가 근거지만 그건 연산자 오버로딩 trait 네임스페이스라
결이 다름(약한 선례). `Op`(단수)는 Slate.js `Op`/Immer patch처럼 "낱개
연산 객체" 지칭에 더 흔해 가장 약함. 최종 결정은 여전히 사용자 몫.
## 반영
`research/operator-sugar-plan.md`에 리서치 결과를 각 해당 절(네임스페이스
이름/포함 범위/`Filtered`)에 인라인으로 통합, Debounce/Throttle을 별도
열린 질문으로 신설. `.claude/question.md` 3번 절 동기화.

View file

@ -0,0 +1,53 @@
# 2026-08-12 스무 번째 세션 — `State` 이름 최종 확정, use-after-destroy 안전망 범위 재검토로 확정 기각
## `State` 이름 확정
사용자가 용어 정리 라운드 1순위였던 `State`를 "그걸로 충분한듯"이라고
확정 — `Computed`/`Derived` 등 대안 검토 종료, 현재 이름 그대로 유지.
`question.md` 1번 절 반영.
## use-after-destroy 검증 안전망 — 재검토 후 최종 기각
**배경**: 직전 세션(열여덟 번째)에서 이미 "고칠 필요 없음"으로 정리했으나,
Claude가 그 문서에 "quad-debug로도 안 커버함"이라고 쓴 문장이 너무 강하게
들려 재확인을 요청했음. 사용자가 이 기회에 "정말 검토할 이유가 있는가"를
근본부터 재검토해달라고 요청, 구체적 근거를 제시.
**사용자 논지**:
1. 제대로 된 use-after-destroy 검증은 (a) 등록된 모든 함수/클로저를
추적해 `inst` 사용을 전부 조사하거나 (b) `inst`를 래핑해 이후의
읽기/쓰기를 가로채야 함 — 이건 quad가 손댈 문제가 아니라 Instance
가상화/추적을 전문으로 하는 rbvm 같은 라이브러리의 영역. quad가
이걸 재발명하면 그 자체로 오버엔지니어링이고, 필요하면 rbvm 같은
전문 도구를 병행해야 함 — quad 혼자 모든 `inst`를 추적하는 건
못 함(Claude 동의).
2. quad-debug는 quad-base/quad-roblox 관점에서 quad 자신이 만들어낸
효과를 설명하는 유틸일 뿐 — 외부에서 Property를 셋팅하는 것 자체는
원래부터 관심사가 아님(`research/debug-tooling-plan.md` "외부 변경
감지" 절이 이미 이 경계를 명시해뒀음을 Claude가 확인).
3. 실제로 use-after-destroy가 발생할 수 있는 자리는, quad가 이미
케어 안 하기로 확정한 요소들(Ref 등)이 정말로 외부로 반출되는
경우뿐 — 이미 권장 안 하는 패턴. Ref는 자식으로 넘기거나 본인이
쓰는 게 관례(React `useRef`와 동급) — **이 관례가 문서에 명시적으로
적혀있지 않았다면 지금 적어달라는 요청.**
4. quad가 세팅한 `Tag`/`Attribute`/`Tween`에는 더 자세한 전용 디버깅
유틸을 제공할 수 있음 — 모든 요소에 `Destroying`을 Connect해서
범용 안전망을 만드는 건 동의 못 함, 그쪽 전용 디버깅에 투자하는 게
나음.
**Claude 검토 결과 — 전부 동의.** 추가로: `Ref`의 사용 관례("만든
컴포넌트 자신이 쓰거나 자식에게 넘김, 경계를 넘어 반출/전역 보관 안 함")가
`base/bind-system-plan.md`의 Ref 절엔 "얻어진 뒤 어떻게 쓰는지는 라이브러리
책임 범위 밖(사용자 자유)"이라고만 적혀있어 정작 그 관례 자체가 문서에
없었던 실제 갭이었음 — 이번에 명시적으로 추가.
## 반영
- `base/bind-system-plan.md`: "Ref — 도입 확정" 절에 "권장 관례" 문단
신설 — useRef급 스코프 관례를 명문화, use-after-destroy가 발생 가능한
유일한 자리로 이 관례 위반을 지목.
- `research/framework-comparison-findings.md`: use-after-destroy 항목을
위 4가지 근거로 재작성(rbvm 위임, quad-debug 스코프 경계, Ref 관례
귀결, Tag/Attribute/Tween 전용 디버깅 투자가 낫다는 판단) — 결론(안전망
안 만듦)은 그대로, 근거가 훨씬 탄탄해짐.
- `.claude/question.md`: `State` 해소 표시 반영.

View file

@ -0,0 +1,49 @@
# 2026-08-12 스물한 번째 세션 — 네이밍 정리 후속: `Pipe` 기각 근거, `Compute` vs `Computed`, `:With`/`Tag`·`Modifier` clone 대조 명문화
**배경**: `State` 이름 확정(스무 번째 세션) 이후 사용자가 두 가지를 더
검토·기록 요청.
## `:With``Tag`/`Modifier`의 clone 체이닝과 혼동될 여지 (직전 턴)
사용자가 `:With`의 clone 여부를 스스로 점검("cloned로 안 보이긴 하는데")
하며 문서화만 좀 더 신경 쓰면 좋겠다고 언급. `bind-system-plan.md`
"`:With`도 새 State 노드" 절 자체는 이미 clone이 아님을 확정해뒀지만,
`Tag`(`:Added`/`:Removed`)·`Modifier`(`:Apply`)가 겉보기엔 같은 `:`
체이닝 문법을 쓰면서 실제로는 clone-then-return이라는 대조가 문서에
명시적으로 없었던 갭 — 헷갈리기 쉬운 지점이라 판단해 같은 절에 "⚠️ 문서
읽을 때 혼동 주의" 문단으로 대조 추가(clone 계열=Tag/Modifier, 새-노드
계열=State의 With/Compute).
## `Pipe``State` 대안으로 검토했으나 기각(사용자가 기각 근거 제시)
1. "캐시한다"는 State의 실제 동작이 파이프라는 비유와 안 맞음 — 파이프는
통과시키는 채널 이미지라 값을 들고/캐시한다는 감각이 잘 안 붙음.
2. 파이프는 흐름/연결의 이미지라, State가 실제로는 각각 주소를 가진
독립된 그래프 노드 단위라는 사실과 안 맞음 — "노드"로 보기 애매해짐.
## `Compute` vs `Computed` — 사용자 직감, Claude 동의 + 근거 보강
사용자 직감: quad의 다른 것들(Tag/Modifier)은 immutable하고 non-lazy해서
호출 즉시 확정되는데, `:Compute`가 만드는 노드는 `fn`을 등록해둘 뿐 실제
계산(`compute`)은 나중에 일어나는 것 같다 — 그러니 진짜 "compute'd"(완료된
상태)가 아닌 것 같다는 느낌.
**Claude 검토 — 동의, 근거를 한 겹 더 정확하게 다듬음**: Vue `computed()`/
Svelte `$derived`도 실제로는 lazy이고 그 이름을 그대로 쓰는 게 각자
생태계에서 문제없음 — 그러니 "-ed는 항상 eager를 뜻한다"는 보편 법칙은
아님. 하지만 **quad 자기 코퍼스 안에서는 다름**: `Tag.Added`/`Removed`,
`Modifier.Overridden`이 이미 "-ed 어미 = clone 후 즉시 확정된 값"이라는
관례를 선점해뒀음(`tag-plan.md`, "`Add`/`Remove`로 쓰면 뮤테이션 API처럼
보이기 때문"). 그 관례가 이미 있는 상태에서 lazy한 State 노드에 같은
어미(`Computed`)를 재사용하면 quad 자신의 기존 신호와 충돌해 "이미 계산
끝난 값"으로 오해하기 쉬움 — 다른 생태계와의 비교가 아니라 quad 내부
일관성 문제. 그래서 `Compute`(동사 원형, "계산을 등록/설정"이라는 뜻)가
`Computed`보다 정확.
## 반영
- `base/bind-system-plan.md`: "`:With`도 새 State 노드" 절에 clone 대조
경고 문단 추가. "여러 Store 값을 묶어 파생값 만들기" 절에 "네이밍 —
`Compute``-ed`가 아닌 이유" 소절 신설.
- `.claude/question.md`: `State` 해소 항목에 `Pipe` 기각 근거 + `Compute`
vs `Computed` 근거를 추가해 보강.

View file

@ -682,3 +682,54 @@ Attribute 자동 unset이 필요해지면 쓸 `:Apply` opt-in 유틸을
typefunction.luau`/`17-modifier-index-tableclone-chaining.luau` 신규
추가(총 17개), `ROADMAP.md` M2/M3/M7 체크리스트에도 누락됐던 항목(디버그
모드 동률 감지+`listHandlers`, `store.key`/`table.clone` 실측 링크) 보강.
**2026-08-12 열여덟 번째 세션 — `framework-comparison-findings.md` 남은
두 항목 "고칠 필요 없음"으로 최종 판단**
(`session/2026-08-12-18-framework-comparison-fixables-closed.md`)
"고칠 만한 것"으로 분류돼 있던 use-after-destroy 검증 안전망 부재,
`:With`의 동적 의존성 미지원 두 항목을 사용자가 확정 판단 — 둘 다 "의도된
트레이드오프"로 문서 3번 절로 이전. use-after-destroy 검증은 `bindLifetime`/
`Effect`로 이미 커버되는 영역에 별도 장치를 얹으면 GC-native 아키텍처와
모순(항상 명시적 Destroy를 강제하게 됨) — 완전한 UB로 남기고 문서화로만
대응. 동적 With는 State immutable 가정과 정면 모순, 실사용 사례도 거의
없음(React `useMemo` deps도 대부분 정적) — 의도적 비지원으로 확정.
`question.md`도 동기화.
**2026-08-12 열아홉 번째 세션 — `Operator` 콤비네이터 슈가 외부 리서치**
(`session/2026-08-12-19-operator-sugar-external-research.md`)
서브 에이전트로 `research/operator-sugar-plan.md``Operator.*` 카탈로그를
다른 리액티브 라이브러리 실제 선례와 대조. 논리(`Not`/`And`/`Or`)/`Sum`/
`Clamp`/`Min`/`Max`는 선례 뚜렷(VueUse `@vueuse/math` 등), 비트연산·비교
연산자·`Sub`/`Div`는 리액티브 콤비네이터로서 선례 전무(드랍 후보). 업계
표준 카테고리인 Debounce/Throttle 부재를 발견했으나 `Blocker`(타이머 없는
값 기반 게이트)와는 다른 메커니즘이라 quad-roblox 쪽 별도 프리미티브
가능성으로 분리. `Filtered`를 Slot 밖에선 plain transform, Slot 안에선
별도 프리미티브로 나눈 기존 판단이 ReactiveUI/SolidJS 선례로 뒷받침됨.
네임스페이스는 Python `operator` 모듈이 `Operator`의 가장 강한 선례 —
최종 확정은 여전히 사용자 몫, `question.md` 3번 동기화.
**2026-08-12 스무 번째 세션 — `State` 이름 최종 확정, use-after-destroy
안전망 근본 재검토 후 최종 기각**
(`session/2026-08-12-20-state-name-final-usedaftedestroy-scoped-out.md`)
용어 정리 1순위였던 `State`를 현재 이름 그대로 유지로 확정
(`Computed`/`Derived` 검토 종료). use-after-destroy는 열여덟 번째
세션에서 이미 "고칠 필요 없음"으로 정리했으나 사용자가 근본부터 재검토
요청 — 일반적 검증은 Instance 가상화/추적이 필요해 rbvm 같은 전문
라이브러리의 영역(quad가 재발명하면 오버엔지니어링), quad-debug는
quad 자신이 만든 효과만 설명하는 스코프라 외부 조작은 원래 관심사
아님(`research/debug-tooling-plan.md`가 이미 명시), 실제 위험 지점은
`Ref`가 관례를 벗어나 반출되는 경우뿐(React `useRef`급 스코프 관례를
`base/bind-system-plan.md`에 이번에 명문화) — 전부 동의로 최종 기각,
근거를 `research/framework-comparison-findings.md`에 보강.
**2026-08-12 스물한 번째 세션 — 네이밍 정리 후속: `Pipe` 기각, `Compute`
vs `Computed`, `:With`/`Tag`·`Modifier` clone 대조 명문화**
(`session/2026-08-12-21-naming-clarity-pipe-compute-with-clone-contrast.md`)
`:With``Tag`/`Modifier`의 clone 체이닝과 겉보기엔 같은 `:` 문법이라
혼동될 여지를 `bind-system-plan.md`에 경고 문단으로 명문화. `State` 대안으로
검토됐던 `Pipe`는 "캐시한다"는 동작이 파이프 비유와 안 맞고 노드 단위로
보기도 애매해 기각. `Compute`(현재 이름) vs `Computed`(만약이었다면) 논의—
Vue/Svelte는 lazy인데도 `computed`를 쓰지만, quad 자기 코퍼스 안에서는
`Tag.Added`/`Modifier.Overridden`이 이미 "-ed = clone 후 즉시 확정된 값"
관례를 선점해뒀어서 lazy한 State에 재사용하면 자기 관례와 충돌 — `Compute`
더 정확하다는 데 동의, `bind-system-plan.md`에 근거 추가.