research: Blocker 프리미티브 채택 확정 — Batch(lexical) 대안, 문서 마무리

세션 마무리 라운드. Batch를 부활시킨 게 아니라 완전히 별개인 새 primitive
Blocker를 채택한 것으로 명확히 분리:
- Batch(함수/코루틴 스코프 lexical block)는 그대로 기각 유지, 반면교사 기록.
- Blocker: 콜스택/코루틴이 아니라 값(On/Off)으로 지연 구간을 표현해 코루틴
  yield 위험을 구조적으로 우회. state:Block(blocker)->state가 호출 즉시
  onunblock 핸들을 등록(지연 등록 아님, 사용자 정정 반영). 이름 확정
  (Blocker/On/Off/IsBlocked/HasBlockedEmit). 재진입(네스팅)은 의도적으로
  미지원 — Rust poisoned-mutex류 위험 회피, 문서화 강조 필수로 기록. 사용
  가이드: 파이프라인 최종 연산 지점에 배치.

documentation-content-map.md에 새 심화/quadnomicon 콘텐츠 후보 반영:
State 파생 체인 동작 원리, :Compute의 조건부 의존값 사용 팁, Blocker 사용
가이드(네스팅 금지 최우선 강조), "왜 Batch 대신 Blocker인가" 비교 에세이,
push-invalidate/pull-recompute의 laziness 설계 철학 심층 에세이.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-07 00:05:18 +09:00
parent 4358fa71d7
commit ccde1cb31c
Signed by: qwreey
GPG key ID: D28DB79297A214BD
3 changed files with 192 additions and 45 deletions

View file

@ -10,16 +10,16 @@
## 지금 열려있는 것 (우선순위순) ## 지금 열려있는 것 (우선순위순)
### 0. 추가 프리미티브 필요성 — 사용자 요청, 대부분 수렴(2026-08-06) ### 0. 추가 프리미티브 필요성 — 사용자 요청, 대부분 수렴(2026-08-06~07)
사용자 질문: "다른 독립 프리미티브나 종속 파생 데이터는 뭐가 더 필요할 것 사용자 질문: "다른 독립 프리미티브나 종속 파생 데이터는 뭐가 더 필요할 것
같나요. 이것만으로 이 프로젝트는 충분하다 생각해요?" — 여러 서브에이전트 같나요. 이것만으로 이 프로젝트는 충분하다 생각해요?" — 여러 서브에이전트
조사 + 사용자와 라이브 논의로 계속 수렴 중, 상세는 `research/ 조사 + 사용자와 라이브 논의로 계속 수렴 중, 상세는 `research/
additional-primitives-plan.md`. 요지: additional-primitives-plan.md`. 요지:
- **키 기반 동적 컬렉션 재조정(유일하게 아직 열려있음, 최우선)**: Fusion - **키 기반 동적 컬렉션 재조정(유일하게 아직 완전히 열려있음, 최우선)**:
`ForPairs`/`ForKeys`/`ForValues`, Vide `indexes()`/`values()`, React Fusion `ForPairs`/`ForKeys`/`ForValues`, Vide `indexes()`/`values()`,
`key` prop에 대응하는 프리미티브가 quad엔 전혀 없음 확인 — React `key` prop에 대응하는 프리미티브가 quad엔 전혀 없음 확인 —
`pre-implementation-audit.md` 1-7번(Slot CRUD 미정의)과 같은 지점이라 `pre-implementation-audit.md` 1-7번(Slot CRUD 미정의)과 같은 지점이라
같이 정의해야 함. **자유 함수**(plain data 또는 State 둘 다 받는 같이 정의해야 함. **자유 함수**(plain data 또는 State 둘 다 받는
폴리모픽 시그니처, State 메소드 프레이밍은 Source 안 쓰는 컴포넌트가 폴리모픽 시그니처, State 메소드 프레이밍은 Source 안 쓰는 컴포넌트가
@ -30,10 +30,21 @@ additional-primitives-plan.md`. 요지:
단순 primitive(재실행 개념 없음, Observer와 별개)로 합의, 시그니처만 단순 primitive(재실행 개념 없음, Observer와 별개)로 합의, 시그니처만
남음. Observer에 cleanup 반환 계약을 얹는 안은 기각(클로저 업밸류로 남음. Observer에 cleanup 반환 계약을 얹는 안은 기각(클로저 업밸류로
이미 충분 — `pre-implementation-audit.md` 3-1과 같은 논리). 이미 충분 — `pre-implementation-audit.md` 3-1과 같은 논리).
- **Batch/Transaction — 프리미티브로 안 만들기로 결정 완료**: lexical - **Blocker — 채택, 핵심 메커니즘+이름 확정(2026-08-07 신설)**: 여러
Source를 한꺼번에 바꿔도 파생값 재계산/재대입이 한 번만 되게 하는
primitive — lexical Batch(아래 항목, 기각)와 달리 콜스택/코루틴이 아니라
**값**(`Blocker` 객체의 `On()`/`Off()`)으로 지연 구간을 표현해 코루틴
yield 위험을 구조적으로 우회함. `state:Block(blocker) -> state`
gated state를 반환(호출 즉시 onunblock 핸들 등록), `IsBlocked`/
`HasBlockedEmit` 필드, 재진입(네스팅)은 의도적으로 미지원(Rust
poisoned-mutex류 위험 회피 — 겹치는 배치는 각자 새 `Blocker`를 쓸 것,
**문서화에서 강하게 명시 필요**). `base/`로 승격 가능한 수준, 남은 건
문서화뿐.
- **Batch(함수/코루틴 스코프 lexical block) — 기각 확정**: lexical
transaction 블록이 코루틴 yield 위에서 구조적으로 위험(전역/코루틴 transaction 블록이 코루틴 yield 위에서 구조적으로 위험(전역/코루틴
스코프 플래그 둘 다 새 코루틴 스폰이나 영구 yield에 깨짐) — 대신 스코프 플래그 둘 다 새 코루틴 스폰이나 영구 yield에 깨짐) — Blocker로
심화 최적화 팁 + quadnomicon "왜 없는가" 에세이로 문서화. 대체됐으므로 더 이상 미해결 문제 아님, quadnomicon "왜 Batch 대신
Blocker인가" 에세이 대상.
- **Context — 기각 확정, 대안이던 레이어드 Store도 철회**: 완전 자동 - **Context — 기각 확정, 대안이던 레이어드 Store도 철회**: 완전 자동
버전은 Roblox Luau 플랫폼 한계로 사실상 불가, 얕은 버전도 Slot 비동기 버전은 Roblox Luau 플랫폼 한계로 사실상 불가, 얕은 버전도 Slot 비동기
추가에서 조용히 깨짐 + quad-debug 철학과 충돌. 레이어드 Store 대안은 추가에서 조용히 깨짐 + quad-debug 철학과 충돌. 레이어드 Store 대안은

View file

@ -1,9 +1,10 @@
# 추가 프리미티브 필요성 조사 — 다른 프레임워크 대비 갭 분석 # 추가 프리미티브 필요성 조사 — 다른 프레임워크 대비 갭 분석
**상태**: research — 사용자와 라이브 논의로 계속 수렴 중(2026-08-06). Context/ **상태**: research — 사용자와 라이브 논의로 수렴(2026-08-06~07). Context/
Batch는 **결정 완료**(둘 다 프리미티브로 안 만듦). 키 기반 컬렉션 재조정은 Batch(lexical)는 **기각 확정**. **Blocker**(새 primitive, Batch의 대안으로
설계 진행 중(사용자가 "작업 전에 모든 정의를 마치고 싶다"고 명시 — M0 전 채택)는 핵심 메커니즘+이름 확정, 문서화만 남음. 키 기반 동적 컬렉션
완전 확정이 목표). Effect는 거의 수렴, 시그니처만 남음. 재조정은 설계 진행 중(사용자가 "작업 전에 모든 정의를 마치고 싶다"고
명시 — M0 전 완전 확정이 목표). Effect는 거의 수렴, 시그니처만 남음.
## 배경 ## 배경
@ -28,7 +29,8 @@ Vide/v1/artworks 소스 근거 조사, Context 구현 난이도 판정) + 그
|---|---|---| |---|---|---|
| 키 기반 동적 컬렉션 재조정 | **진짜 빈 자리, 최우선** | 설계 진행 중 | | 키 기반 동적 컬렉션 재조정 | **진짜 빈 자리, 최우선** | 설계 진행 중 |
| Effect(leaf 죽음에 확정 정리) | 진짜 빈 자리 | 거의 수렴, 시그니처만 남음 | | Effect(leaf 죽음에 확정 정리) | 진짜 빈 자리 | 거의 수렴, 시그니처만 남음 |
| Batch/Transaction | **프리미티브로 안 만듦** — 문서화로 대체 | 결정 완료 | | **Blocker(값 기반 emit 지연/합치기)** | **채택** — Batch의 대안 | 핵심 메커니즘+이름 확정, 문서화만 남음 |
| Batch(함수/코루틴 스코프 lexical block) | **기각** | 결정 완료 |
| Context(트리 하위 암묵 전파) | **기각** — 대안(레이어드 Store)도 철회 | 결정 완료 | | Context(트리 하위 암묵 전파) | **기각** — 대안(레이어드 Store)도 철회 | 결정 완료 |
| Observer에 cleanup 반환 계약 추가 | **기각** — 클로저 업밸류로 이미 충분 | 결정 완료 | | Observer에 cleanup 반환 계약 추가 | **기각** — 클로저 업밸류로 이미 충분 | 결정 완료 |
| Untrack/Peek | 빈 자리 아님 | - | | Untrack/Peek | 빈 자리 아님 | - |
@ -192,7 +194,13 @@ table로 되는 Observer보다 비쌈) — 사용자가 필요할 때만 쓰는
**상태**: 사실상 수렴 — 시그니처(`fn`의 인자 유무, `EffectHandle`의 모양)만 **상태**: 사실상 수렴 — 시그니처(`fn`의 인자 유무, `EffectHandle`의 모양)만
다듬으면 `base/`로 승격 가능해 보임. 계속 논의 원하면 이어감. 다듬으면 `base/`로 승격 가능해 보임. 계속 논의 원하면 이어감.
## 3. Batch/Transaction — 프리미티브로 안 만듦 (결정 완료) ## 3. Batch(함수/코루틴 스코프 lexical block) — 기각 확정
**중요**: 아래는 "값 기반 지연/합치기"라는 문제 자체를 기각한 게 아니라,
**그 문제를 lexical block(Solid `batch()`/MobX `runInAction()`류)으로
풀려는 접근만** 기각한 것이다. 실제 해법은 완전히 다른 별개 primitive인
**Blocker**(아래 3-1번)로 채택됨 — 이 절은 "왜 lexical 접근은 안 되는가"만
다루는 순수 반면교사 기록으로 남긴다.
### "즉시 pull"이 뭔지 (참고용 예시) ### "즉시 pull"이 뭔지 (참고용 예시)
@ -209,7 +217,7 @@ b:Set(2) -- total 무효화 → 핸들러가 즉시 (av=1, bv=2)로 다시 재
-- BackgroundColor3가 중간값을 한 번 거쳐가고, 두 번 대입됨 -- BackgroundColor3가 중간값을 한 번 거쳐가고, 두 번 대입됨
``` ```
### 왜 lexical block 방식(Solid `batch()`/MobX `runInAction()`)을 안 쓰는가 ### 왜 lexical block 방식을 안 쓰는가
`Batch(fn)`을 "플래그 세우고 fn 실행, 끝나면 flush"로 구현하면 **fn이 `Batch(fn)`을 "플래그 세우고 fn 실행, 끝나면 flush"로 구현하면 **fn이
yield하는 순간 위험해진다**(사용자 지적, 정확함): yield하는 순간 위험해진다**(사용자 지적, 정확함):
@ -224,20 +232,108 @@ yield하는 순간 위험해진다**(사용자 지적, 정확함):
pull보다 더 나쁜 실패 모드. pull보다 더 나쁜 실패 모드.
이건 구현을 잘 짜서 피할 수 있는 버그가 아니라 **lexical block 모델 이건 구현을 잘 짜서 피할 수 있는 버그가 아니라 **lexical block 모델
자체가 협조적 스케줄링 환경과 구조적으로 안 맞는 것**으로 판단. 자체가 협조적 스케줄링 환경과 구조적으로 안 맞는 것**으로 판단, 기각.
### 결정: 프리미티브 없음, 대신 문서화 두 갈래 ## 3-1. Blocker — 채택된 새 primitive (Batch의 대안, 2026-08-06~07 세션)
1. **심화(사용자 대상) 최적화 팁**: "여러 Source를 한꺼번에 바꿀 때 ### 핵심 아이디어 — 콜스택/코루틴이 아니라 값으로 지연 구간을 표현
store-bind가 걸린 파생값의 재계산/재대입이 중복되지 않도록 파이프라인
구성과 업데이트 순서에 유의하라"를 실용 가이드로 문서화. 예: 정말 Batch가 실패하는 근본 이유는 "지연 구간"을 콜스택/코루틴 스코프로
여러 값이 자주 같이 바뀐다면 애초에 하나의 State로 묶어서 `:Set()` 표현하려 했기 때문이다. `Blocker`는 그 구간을 **그냥 사용자가 들고 있는
한 번만 하는 설계를 권장. 값**으로 표현한다 — `On()`/`Off()`를 부르는 두 시점 사이에 얼마나 많은
2. **quadnomicon(프레임워크 설계자 대상) 에세이**: "왜 Batch/Transaction이 yield/코루틴 전환이 끼어도, 심지어 완전히 다른 코루틴에서 `Off()`
없는가" — 위 코루틴 yield 위험 분석을 근거로, Solid/MobX식 lexical 불러도 아무 문제가 없다. Batch를 무너뜨렸던 세 가지 실패 모드(전역 플래그
transaction이 협조적 스케줄링(코루틴) 환경에서 왜 근본적으로 위험한지 오염, 새 코루틴 미상속, 영구 yield로 인한 무기한 방치)가 구조적으로 전부
설명. `documentation-content-map.md`에 후보로 등록함(아래 "문서화 해당 안 됨.
백로그" 참고).
### 메커니즘 (확정)
```
Blocker() -> blocker -- 생성자
blocker:On() -> self -- IsBlocked = true로만 설정, 그 외 아무것도 안 함
blocker:Off() -> self -- IsBlocked = false로 먼저 설정, 그 다음 등록된
-- onunblock 핸들 전부 실행(순서 무관, idempotent)
state:Block(blocker) -> state -- 새 gated state 반환. **호출되는 즉시**(나중에
-- 처음 블록될 때가 아니라) onunblock 핸들을
-- blocker의 weak 배열에 등록.
```
gated state의 동작:
- 원본 state가 emit(무효화)될 때, 이 gated state로 전파를 시도.
- `blocker.IsBlocked`이면: 전파 안 하고 `HasBlockedEmit = true`만 세팅.
- `blocker.IsBlocked`가 아니면: 평소처럼 그냥 전파(투명하게 통과).
- `blocker:Off()`가 실행하는 onunblock 핸들은: `HasBlockedEmit`을 확인해
true면 그제서야 정확히 1회 전파(emit)하고 플래그를 리셋. 이미 false면
아무 것도 안 함(이미 언블록 상태에서 다시 Off를 불러도 안전 —
idempotent).
**`:Get()`엔 영향 없음** — 블록은 emit **전파**(eager 소비자에게 "바뀌었다"
알리는 신호)만 지연시킨다. 블록 중이라도 누군가 명시적으로 `:Get()`하면
그 순간의 실제 값을 정상적으로 계산해서 준다 — `store-semantics.md`
"Get()은 라이브 레퍼런스를 준다" 원칙과 일치.
### 사용 예시 — `state1, state2 -> state3` 케이스의 정답
처음 문제 제기("state1, state2 -> state3로 갈 때 둘 다 업데이트하면 state3가
두 번 계산됨, 한번에 할 방법이 없다")에 대한 답: **`state3`(결합된 결과)
하나에만 `:Block`을 걸면 된다** — `state1`/`state2` 각각에 걸 필요 없음:
```lua
local blocker = Blocker()
local gated3 = state3:Block(blocker) -- 소비자는 gated3를 구독
blocker:On()
state1:Set(1) -- state3 무효화 → gated3로 전파 시도 → 블록됨 → HasBlockedEmit=true
state2:Set(2) -- state3 무효화 → gated3로 전파 시도 → 이미 true, 그대로
blocker:Off() -- onunblock 핸들 실행 → HasBlockedEmit 확인 → 딱 한 번 emit
```
**일반 사용 가이드(확정, 문서화 필수)**: Block은 **파이프라인의 최종
연산 지점**(실제로 무거운 계산이 일어나는 derived state, eager 소비자에
가장 가까운 지점)에 거는 게 원칙 — 소스가 여러 개든, 하나가 한 주기에
여러 번 바뀌든 상관없이 이 지점 하나만 지키면 됨. 소스 쪽에 각각 거는 게
아니다.
### 이름 확정
- 클래스: `Blocker``Observer`/`Modifier`/`Ref`와 같은 명사-행위자
네이밍 관례와 일치.
- Blocker 자신의 토글: **`On()`/`Off()` -> self** (`Block()`/`Unblock()`
아님) — `state:Block(blocker)`가 이미 "배선(wiring)" 동작의 동사로
"Block"을 쓰고 있어서, Blocker 자신의 토글까지 같은 단어를 쓰면
`blocker:Block()`(블로커를 켠다)과 `state:Block(blocker)`(state를 이
블로커에 배선한다)가 같은 단어로 다른 두 동작을 가리키게 됨 — 자체
API 안에서 `register`→`State` 리네임 때 겪었던 "모호함은 풀었는데
충돌이 새로 생긴" 패턴이 반복될 뻔한 걸 미리 피함.
- 필드: **`IsBlocked`**(Blocker 자신의 On/Off 상태) — `Enabled`보다
명확(enabled는 "정상 작동 중"으로도 읽혀 헷갈릴 수 있음).
- 필드: **`HasBlockedEmit`**(gated state의 대기 플래그) — `Is`/`Has`
접두어로 불리언임을 바로 알려줌.
- 메소드: `state:Block(blocker) -> state`.
### 재진입(네스팅) — 의도적으로 미지원, 강한 문서화 필수
`IsBlocked`는 카운터가 아니라 단순 불리언이고, **의도적으로 그렇게
둔다.** 레퍼런스 카운팅으로 네스팅을 지원할 수도 있었지만, 그러면
"`On()` 여러 번, `Off()` 실수로 적게" 같은 버그가 **영구 블록으로 조용히
새는** 더 위험한 실패 모드를 만든다. 언어 차원에서 "On~Off 사이 코드가
죽었는지, 스레드가 죽었는지"를 추적하는 것도 해키해서 하지 않기로 함(Rust의
"poisoned mutex" — 락 구간 안에서 패닉이 나면 락이 오염 상태가 되는 것과
유사한 문제의식, 그 트래킹 자체를 만들지 않기로 함).
**대신 확정된 규칙**: 겹치는 배치가 필요하면 **각자 새 `Blocker` 인스턴스를
만들 것** — 하나의 Blocker를 여러 컨텍스트에서 재사용/중첩하지 않는다.
`Off()`는 스태킹 없이 즉시 그 자리에서 꺼진다. **이 제약은 반드시 사용자
문서(API 레퍼런스 수준)에 명시적으로 강조할 것** — 네스팅을 시도하면
조용히 잘못된 시점에 조기 해제되는, 원인 추적이 어려운 버그로 이어짐.
### 상태: 핵심 메커니즘+이름 확정. 남은 건 문서화뿐
`Extract`/`Add`처럼 세부 시그니처가 더 필요한 다른 항목들과 달리, Blocker는
설계 질문이 남아있지 않음 — `base/`로 승격 가능한 수준. quadnomicon에서
"Batch를 기각하고 왜 Blocker로 갔는가"를 비교 설명하는 게 좋은 소재(아래
"문서화 백로그" 참고).
## 4. Context — 기각 확정, 레이어드 Store 대안도 철회 (결정 완료) ## 4. Context — 기각 확정, 레이어드 Store 대안도 철회 (결정 완료)
@ -312,24 +408,39 @@ Context, 레이어드 Store 둘 다 프리미티브로 만들지 않음. "왜 Co
- **디바운스/스로틀**: Fusion/Vide/v1 어디에도 공개 프리미티브로 없음 — - **디바운스/스로틀**: Fusion/Vide/v1 어디에도 공개 프리미티브로 없음 —
세 레포 모두 없다는 것 자체가 "quad도 굳이 안 만들어도 된다"는 정황. 세 레포 모두 없다는 것 자체가 "quad도 굳이 안 만들어도 된다"는 정황.
## 문서화 백로그 (2026-08-06, `documentation-content-map.md`에도 반영) ## 문서화 백로그 (2026-08-06~07, `documentation-content-map.md`에도 반영)
- **quadnomicon 에세이**: "왜 Batch/Transaction이 없는가"(코루틴 yield - **quadnomicon 에세이**:
위험), "왜 Context가 없는가"(명시적 타입 강제 Store 전달이 이미 그 - "왜 lexical Batch를 기각하고 대신 값 기반 Blocker를 택했는가" — 코루틴
역할을 함). yield 위험 분석(Batch 절)과 Blocker의 설계(3-1절)를 나란히 비교.
- **심화 문서**: "여러 Source를 한꺼번에 바꿀 때 중복 재계산/재대입을 - "왜 Context가 없는가"(명시적 타입 강제 Store 전달이 이미 그 역할을 함).
피하는 파이프라인/업데이트 순서 최적화 팁"(Batch 없이 사용자가 직접 - "왜 push-invalidate/pull-recompute 설계가 laziness와 재계산 방지를
할 수 있는 것). 최우선 목표로 뒀는가" — Blocker 같은 파생 프리미티브가 이 목표 위에서
자연스럽게 나온 이유까지 포함해 기존 심화 콘텐츠 후보 3번(`왜
push-invalidate/pull-recompute인가`)을 더 깊게 확장.
- **심화 문서**:
- "State 파생 체인 동작 원리" — emit이 아래로 전파되고, `Get()` 요청이
위로 거슬러 올라가 재계산된 뒤 다시 아래로 내려오는 흐름을 명확히
설명(Blocker/Effect 둘 다 이 흐름 위에서 동작하므로 선행 이해로 필요).
- "`:Compute` 함수 안에서 `if` 등으로 일부 의존값만 조건부로 사용하는
유연한 구조" — 명시적 의존성 선언(`:With`) 위에서도 실제 계산은
조건부로 일부만 쓸 수 있다는 팁.
- "Blocker 사용 가이드" — 파이프라인 최종 연산 지점에 배치, **네스팅
금지를 강하게 명시**(위 3-1절 "재진입" 참고, 문서화 시 최우선 강조
항목).
- "여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산을 피하는
파이프라인/업데이트 순서 팁"(Blocker를 안 쓰는 단순 케이스용 보조 팁,
기존 "심화 최적화 팁" 항목을 Blocker 존재를 전제로 재조정).
## 제안 우선순위 ## 제안 우선순위
1. **키 기반 컬렉션 재조정** — 여전히 최우선, 설계 진행 중. M0 이전 완전 1. **키 기반 컬렉션 재조정** — 여전히 최우선, 설계 진행 중. M0 이전 완전
확정이 목표. 확정이 목표.
2. **Effect** — 거의 수렴, 시그니처만 다듬으면 됨. 2. **Effect** — 거의 수렴, 시그니처만 다듬으면 됨.
3. **Batch** — 결정 완료(프리미티브 없음, 문서화로 대체), 더 이상 검토 3. **Blocker** — 핵심 메커니즘+이름 확정, `base/`로 승격 가능한 수준.
대상 아님. 문서화(특히 네스팅 금지 강조)만 남음.
4. **Context** — 결정 완료(기각, 레이어드 Store도 철회), 더 이상 검토 4. **Batch(lexical)/Context** — 둘 다 결정 완료(기각), 더 이상 검토 대상
대상 아님. 아님.
## 참고: 조사에 사용한 소스 근거 ## 참고: 조사에 사용한 소스 근거

View file

@ -122,7 +122,23 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부
12. 독립 프리미티브 vs 파생 데이터 — 생성자 모양을 결정하는 원칙 — `store-semantics.md` 12. 독립 프리미티브 vs 파생 데이터 — 생성자 모양을 결정하는 원칙 — `store-semantics.md`
14. 왜 컴포넌트는 전역 store를 직접 참조하면 안 되는가(이식성) — `purity-and-effects-plan.md` 14. 왜 컴포넌트는 전역 store를 직접 참조하면 안 되는가(이식성) — `purity-and-effects-plan.md`
15. 왜 Source가 State를 구조적으로 만족하는가(Svelte Writable/Readable과 같은 서브타입 모양, `RefSource`/`StoreSource` 중간안이 왜 기각됐는가) — `store-semantics.md`, `component-composition-plan.md` 15. 왜 Source가 State를 구조적으로 만족하는가(Svelte Writable/Readable과 같은 서브타입 모양, `RefSource`/`StoreSource` 중간안이 왜 기각됐는가) — `store-semantics.md`, `component-composition-plan.md`
16. 여러 Source를 한꺼번에 바꿀 때 중복 재계산/재대입을 피하는 파이프라인/업데이트 순서 최적화 팁(Batch 프리미티브 없이 사용자가 직접 할 수 있는 것) — `research/additional-primitives-plan.md` "문서화 백로그" 절(2026-08-06 신설) 16. **State 파생 체인 동작 원리** — emit이 아래로 전파되고, `Get()` 요청이
위로 거슬러 올라가 재계산된 뒤 다시 아래로 내려오는 흐름을 명확히
설명(Blocker/Effect 둘 다 이 흐름 위에서 동작하므로 선행 이해로 필요)
`research/additional-primitives-plan.md` "문서화 백로그" 절
(2026-08-06~07 신설)
17. **`:Compute` 함수 안에서 `if` 등으로 일부 의존값만 조건부로 사용하는
유연한 구조** — 명시적 의존성 선언(`:With`) 위에서도 실제 계산은
조건부로 일부만 쓸 수 있다는 팁 — `research/additional-primitives-plan.md`
"문서화 백로그" 절
18. **Blocker 사용 가이드** — 파이프라인 최종 연산 지점(무거운 계산이
실제 일어나는 derived state)에 배치하는 게 원칙이라는 것, **네스팅
금지를 최우선으로 강조**(겹치는 배치는 각자 새 `Blocker`를 만들 것 —
안 지키면 조용히 잘못된 시점에 조기 해제되는 원인 추적 어려운 버그로
이어짐) — `research/additional-primitives-plan.md` 3-1절
19. 여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산/재대입을
피하는 파이프라인/업데이트 순서 팁(Blocker를 안 쓰는 단순 케이스용
보조 팁) — `research/additional-primitives-plan.md` "문서화 백로그" 절
(13번이었던 "Fusion/Vide 경험자용 비교 섹션"은 2026-08-06 재분류로 아래 6번 (13번이었던 "Fusion/Vide 경험자용 비교 섹션"은 2026-08-06 재분류로 아래 6번
`quadnomicon`으로 이동) `quadnomicon`으로 이동)
@ -142,19 +158,28 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부
2. `:With`+`:Compute` 명시적 파생값이 Vide의 암묵적 ambient stack 대신 2. `:With`+`:Compute` 명시적 파생값이 Vide의 암묵적 ambient stack 대신
채택된 이유 — Vide 경험자 대상 비교 채택된 이유 — Vide 경험자 대상 비교
**2026-08-06 후속 세션에서 추가된 후보(둘 다 `research/ **2026-08-06~07 후속 세션에서 추가된 후보(전부 `research/
additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**: additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**:
3. 왜 Batch/Transaction이 없는가 — Solid `batch()`/MobX `runInAction()` 3. **왜 lexical Batch를 기각하고 대신 값 기반 Blocker를 택했는가**
lexical transaction이 Roblox의 협조적 스케줄링(코루틴 yield) 환경에서 Solid `batch()`/MobX `runInAction()`류 lexical transaction이 Roblox의
왜 근본적으로 위험한지(전역/코루틴 스코프 플래그가 새 코루틴 스폰·영구 협조적 스케줄링(코루틴 yield) 환경에서 왜 근본적으로 위험한지(전역/
yield에 어떻게 깨지는지 구체 시나리오 포함) — Fusion/Vide 비교는 아니고 코루틴 스코프 플래그가 새 코루틴 스폰·영구 yield에 어떻게 깨지는지
"설계 원리"형 에세이라 Rustonomicon 패러디 취지(비슷한 프레임워크 구체 시나리오) **+** 그 대안으로 `Blocker`(콜스택/코루틴이 아니라
설계자용)와 잘 맞음 값으로 지연 구간을 표현, 네스팅 의도적 미지원)가 어떻게 같은 문제를
구조적으로 우회하는지 나란히 비교 — Fusion/Vide 비교는 아니고 "설계
원리"형 에세이라 Rustonomicon 패러디 취지(비슷한 프레임워크 설계자용)와
잘 맞음. (2026-08-06 세션엔 "왜 Batch가 없는가"로만 다뤘다가, Blocker
채택 후 2026-08-07 세션에서 비교 에세이로 재구성됨 — Batch(lexical)
기각과 Blocker 채택은 별개 결정이니 혼동하지 말 것.)
4. 왜 Context가 없는가 — 얕은 버전(코루틴 키 weak table push-pop)조차 4. 왜 Context가 없는가 — 얕은 버전(코루틴 키 weak table push-pop)조차
quad가 정상 패턴으로 확정한 Slot 비동기 추가에서 조용히 깨지는 이유, quad가 정상 패턴으로 확정한 Slot 비동기 추가에서 조용히 깨지는 이유,
완전 자동 버전이 Roblox Luau의 플랫폼 한계(thread-local 없음)로 불가한 완전 자동 버전이 Roblox Luau의 플랫폼 한계(thread-local 없음)로 불가한
이유, 명시적 타입 강제 Store 전달이 Context보다 안전한 이유(레이어드 이유, 명시적 타입 강제 Store 전달이 Context보다 안전한 이유(레이어드
Store 대안도 왜 함께 기각됐는지 포함) Store 대안도 왜 함께 기각됐는지 포함)
5. 왜 push-invalidate/pull-recompute 설계가 laziness와 재계산 방지를
최우선 목표로 뒀는가 — 위 `심화` 3번(`왜 push-invalidate/pull-recompute
인가`)을 더 깊게 확장, `Blocker` 같은 파생 프리미티브가 이 목표 위에서
왜 자연스럽게 나왔는지까지 포함하는 설계 철학 에세이
**publish 안 하는 것과의 경계**: 세션별 정정 이력, 조사 원자료(Fusion **publish 안 하는 것과의 경계**: 세션별 정정 이력, 조사 원자료(Fusion
반응 그래프 BFS 분석, quad2-try 죽은 코드 조사 등)는 quadnomicon에도 반응 그래프 BFS 분석, quad2-try 죽은 코드 조사 등)는 quadnomicon에도