diff --git a/.claude/question.md b/.claude/question.md index 42ed0fb..2acd39e 100644 --- a/.claude/question.md +++ b/.claude/question.md @@ -10,16 +10,16 @@ ## 지금 열려있는 것 (우선순위순) -### 0. 추가 프리미티브 필요성 — 사용자 요청, 대부분 수렴(2026-08-06) +### 0. 추가 프리미티브 필요성 — 사용자 요청, 대부분 수렴(2026-08-06~07) 사용자 질문: "다른 독립 프리미티브나 종속 파생 데이터는 뭐가 더 필요할 것 같나요. 이것만으로 이 프로젝트는 충분하다 생각해요?" — 여러 서브에이전트 조사 + 사용자와 라이브 논의로 계속 수렴 중, 상세는 `research/ additional-primitives-plan.md`. 요지: -- **키 기반 동적 컬렉션 재조정(유일하게 아직 열려있음, 최우선)**: Fusion - `ForPairs`/`ForKeys`/`ForValues`, Vide `indexes()`/`values()`, React - `key` prop에 대응하는 프리미티브가 quad엔 전혀 없음 확인 — +- **키 기반 동적 컬렉션 재조정(유일하게 아직 완전히 열려있음, 최우선)**: + Fusion `ForPairs`/`ForKeys`/`ForValues`, Vide `indexes()`/`values()`, + React `key` prop에 대응하는 프리미티브가 quad엔 전혀 없음 확인 — `pre-implementation-audit.md` 1-7번(Slot CRUD 미정의)과 같은 지점이라 같이 정의해야 함. **자유 함수**(plain data 또는 State 둘 다 받는 폴리모픽 시그니처, State 메소드 프레이밍은 Source 안 쓰는 컴포넌트가 @@ -30,10 +30,21 @@ additional-primitives-plan.md`. 요지: 단순 primitive(재실행 개념 없음, Observer와 별개)로 합의, 시그니처만 남음. Observer에 cleanup 반환 계약을 얹는 안은 기각(클로저 업밸류로 이미 충분 — `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 위에서 구조적으로 위험(전역/코루틴 - 스코프 플래그 둘 다 새 코루틴 스폰이나 영구 yield에 깨짐) — 대신 - 심화 최적화 팁 + quadnomicon "왜 없는가" 에세이로 문서화. + 스코프 플래그 둘 다 새 코루틴 스폰이나 영구 yield에 깨짐) — Blocker로 + 대체됐으므로 더 이상 미해결 문제 아님, quadnomicon "왜 Batch 대신 + Blocker인가" 에세이 대상. - **Context — 기각 확정, 대안이던 레이어드 Store도 철회**: 완전 자동 버전은 Roblox Luau 플랫폼 한계로 사실상 불가, 얕은 버전도 Slot 비동기 추가에서 조용히 깨짐 + quad-debug 철학과 충돌. 레이어드 Store 대안은 diff --git a/.claude/research/additional-primitives-plan.md b/.claude/research/additional-primitives-plan.md index 2d5be45..636b420 100644 --- a/.claude/research/additional-primitives-plan.md +++ b/.claude/research/additional-primitives-plan.md @@ -1,9 +1,10 @@ # 추가 프리미티브 필요성 조사 — 다른 프레임워크 대비 갭 분석 -**상태**: research — 사용자와 라이브 논의로 계속 수렴 중(2026-08-06). Context/ -Batch는 **결정 완료**(둘 다 프리미티브로 안 만듦). 키 기반 컬렉션 재조정은 -설계 진행 중(사용자가 "작업 전에 모든 정의를 마치고 싶다"고 명시 — M0 전 -완전 확정이 목표). Effect는 거의 수렴, 시그니처만 남음. +**상태**: research — 사용자와 라이브 논의로 수렴(2026-08-06~07). Context/ +Batch(lexical)는 **기각 확정**. **Blocker**(새 primitive, Batch의 대안으로 +채택)는 핵심 메커니즘+이름 확정, 문서화만 남음. 키 기반 동적 컬렉션 +재조정은 설계 진행 중(사용자가 "작업 전에 모든 정의를 마치고 싶다"고 +명시 — M0 전 완전 확정이 목표). Effect는 거의 수렴, 시그니처만 남음. ## 배경 @@ -28,7 +29,8 @@ Vide/v1/artworks 소스 근거 조사, Context 구현 난이도 판정) + 그 |---|---|---| | 키 기반 동적 컬렉션 재조정 | **진짜 빈 자리, 최우선** | 설계 진행 중 | | Effect(leaf 죽음에 확정 정리) | 진짜 빈 자리 | 거의 수렴, 시그니처만 남음 | -| Batch/Transaction | **프리미티브로 안 만듦** — 문서화로 대체 | 결정 완료 | +| **Blocker(값 기반 emit 지연/합치기)** | **채택** — Batch의 대안 | 핵심 메커니즘+이름 확정, 문서화만 남음 | +| Batch(함수/코루틴 스코프 lexical block) | **기각** | 결정 완료 | | Context(트리 하위 암묵 전파) | **기각** — 대안(레이어드 Store)도 철회 | 결정 완료 | | Observer에 cleanup 반환 계약 추가 | **기각** — 클로저 업밸류로 이미 충분 | 결정 완료 | | Untrack/Peek | 빈 자리 아님 | - | @@ -192,7 +194,13 @@ table로 되는 Observer보다 비쌈) — 사용자가 필요할 때만 쓰는 **상태**: 사실상 수렴 — 시그니처(`fn`의 인자 유무, `EffectHandle`의 모양)만 다듬으면 `base/`로 승격 가능해 보임. 계속 논의 원하면 이어감. -## 3. Batch/Transaction — 프리미티브로 안 만듦 (결정 완료) +## 3. Batch(함수/코루틴 스코프 lexical block) — 기각 확정 + +**중요**: 아래는 "값 기반 지연/합치기"라는 문제 자체를 기각한 게 아니라, +**그 문제를 lexical block(Solid `batch()`/MobX `runInAction()`류)으로 +풀려는 접근만** 기각한 것이다. 실제 해법은 완전히 다른 별개 primitive인 +**Blocker**(아래 3-1번)로 채택됨 — 이 절은 "왜 lexical 접근은 안 되는가"만 +다루는 순수 반면교사 기록으로 남긴다. ### "즉시 pull"이 뭔지 (참고용 예시) @@ -209,7 +217,7 @@ b:Set(2) -- total 무효화 → 핸들러가 즉시 (av=1, bv=2)로 다시 재 -- BackgroundColor3가 중간값을 한 번 거쳐가고, 두 번 대입됨 ``` -### 왜 lexical block 방식(Solid `batch()`/MobX `runInAction()`)을 안 쓰는가 +### 왜 lexical block 방식을 안 쓰는가 `Batch(fn)`을 "플래그 세우고 fn 실행, 끝나면 flush"로 구현하면 **fn이 yield하는 순간 위험해진다**(사용자 지적, 정확함): @@ -224,20 +232,108 @@ yield하는 순간 위험해진다**(사용자 지적, 정확함): pull보다 더 나쁜 실패 모드. 이건 구현을 잘 짜서 피할 수 있는 버그가 아니라 **lexical block 모델 -자체가 협조적 스케줄링 환경과 구조적으로 안 맞는 것**으로 판단. +자체가 협조적 스케줄링 환경과 구조적으로 안 맞는 것**으로 판단, 기각. -### 결정: 프리미티브 없음, 대신 문서화 두 갈래 +## 3-1. Blocker — 채택된 새 primitive (Batch의 대안, 2026-08-06~07 세션) -1. **심화(사용자 대상) 최적화 팁**: "여러 Source를 한꺼번에 바꿀 때 - store-bind가 걸린 파생값의 재계산/재대입이 중복되지 않도록 파이프라인 - 구성과 업데이트 순서에 유의하라"를 실용 가이드로 문서화. 예: 정말 - 여러 값이 자주 같이 바뀐다면 애초에 하나의 State로 묶어서 `:Set()`을 - 한 번만 하는 설계를 권장. -2. **quadnomicon(프레임워크 설계자 대상) 에세이**: "왜 Batch/Transaction이 - 없는가" — 위 코루틴 yield 위험 분석을 근거로, Solid/MobX식 lexical - transaction이 협조적 스케줄링(코루틴) 환경에서 왜 근본적으로 위험한지 - 설명. `documentation-content-map.md`에 후보로 등록함(아래 "문서화 - 백로그" 참고). +### 핵심 아이디어 — 콜스택/코루틴이 아니라 값으로 지연 구간을 표현 + +Batch가 실패하는 근본 이유는 "지연 구간"을 콜스택/코루틴 스코프로 +표현하려 했기 때문이다. `Blocker`는 그 구간을 **그냥 사용자가 들고 있는 +값**으로 표현한다 — `On()`/`Off()`를 부르는 두 시점 사이에 얼마나 많은 +yield/코루틴 전환이 끼어도, 심지어 완전히 다른 코루틴에서 `Off()`를 +불러도 아무 문제가 없다. Batch를 무너뜨렸던 세 가지 실패 모드(전역 플래그 +오염, 새 코루틴 미상속, 영구 yield로 인한 무기한 방치)가 구조적으로 전부 +해당 안 됨. + +### 메커니즘 (확정) + +``` +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 대안도 철회 (결정 완료) @@ -312,24 +408,39 @@ Context, 레이어드 Store 둘 다 프리미티브로 만들지 않음. "왜 Co - **디바운스/스로틀**: Fusion/Vide/v1 어디에도 공개 프리미티브로 없음 — 세 레포 모두 없다는 것 자체가 "quad도 굳이 안 만들어도 된다"는 정황. -## 문서화 백로그 (2026-08-06, `documentation-content-map.md`에도 반영) +## 문서화 백로그 (2026-08-06~07, `documentation-content-map.md`에도 반영) -- **quadnomicon 에세이**: "왜 Batch/Transaction이 없는가"(코루틴 yield - 위험), "왜 Context가 없는가"(명시적 타입 강제 Store 전달이 이미 그 - 역할을 함). -- **심화 문서**: "여러 Source를 한꺼번에 바꿀 때 중복 재계산/재대입을 - 피하는 파이프라인/업데이트 순서 최적화 팁"(Batch 없이 사용자가 직접 - 할 수 있는 것). +- **quadnomicon 에세이**: + - "왜 lexical Batch를 기각하고 대신 값 기반 Blocker를 택했는가" — 코루틴 + yield 위험 분석(Batch 절)과 Blocker의 설계(3-1절)를 나란히 비교. + - "왜 Context가 없는가"(명시적 타입 강제 Store 전달이 이미 그 역할을 함). + - "왜 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 이전 완전 확정이 목표. 2. **Effect** — 거의 수렴, 시그니처만 다듬으면 됨. -3. **Batch** — 결정 완료(프리미티브 없음, 문서화로 대체), 더 이상 검토 - 대상 아님. -4. **Context** — 결정 완료(기각, 레이어드 Store도 철회), 더 이상 검토 - 대상 아님. +3. **Blocker** — 핵심 메커니즘+이름 확정, `base/`로 승격 가능한 수준. + 문서화(특히 네스팅 금지 강조)만 남음. +4. **Batch(lexical)/Context** — 둘 다 결정 완료(기각), 더 이상 검토 대상 + 아님. ## 참고: 조사에 사용한 소스 근거 diff --git a/.claude/research/documentation-content-map.md b/.claude/research/documentation-content-map.md index 5a9319f..04a5836 100644 --- a/.claude/research/documentation-content-map.md +++ b/.claude/research/documentation-content-map.md @@ -122,7 +122,23 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부 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` -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번 `quadnomicon`으로 이동) @@ -142,19 +158,28 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부 2. `:With`+`:Compute` 명시적 파생값이 Vide의 암묵적 ambient stack 대신 채택된 이유 — Vide 경험자 대상 비교 -**2026-08-06 후속 세션에서 추가된 후보(둘 다 `research/ +**2026-08-06~07 후속 세션에서 추가된 후보(전부 `research/ additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**: -3. 왜 Batch/Transaction이 없는가 — Solid `batch()`/MobX `runInAction()`류 - lexical transaction이 Roblox의 협조적 스케줄링(코루틴 yield) 환경에서 - 왜 근본적으로 위험한지(전역/코루틴 스코프 플래그가 새 코루틴 스폰·영구 - yield에 어떻게 깨지는지 구체 시나리오 포함) — Fusion/Vide 비교는 아니고 - "설계 원리"형 에세이라 Rustonomicon 패러디 취지(비슷한 프레임워크 - 설계자용)와 잘 맞음 +3. **왜 lexical Batch를 기각하고 대신 값 기반 Blocker를 택했는가** — + Solid `batch()`/MobX `runInAction()`류 lexical transaction이 Roblox의 + 협조적 스케줄링(코루틴 yield) 환경에서 왜 근본적으로 위험한지(전역/ + 코루틴 스코프 플래그가 새 코루틴 스폰·영구 yield에 어떻게 깨지는지 + 구체 시나리오) **+** 그 대안으로 `Blocker`(콜스택/코루틴이 아니라 + 값으로 지연 구간을 표현, 네스팅 의도적 미지원)가 어떻게 같은 문제를 + 구조적으로 우회하는지 나란히 비교 — Fusion/Vide 비교는 아니고 "설계 + 원리"형 에세이라 Rustonomicon 패러디 취지(비슷한 프레임워크 설계자용)와 + 잘 맞음. (2026-08-06 세션엔 "왜 Batch가 없는가"로만 다뤘다가, Blocker + 채택 후 2026-08-07 세션에서 비교 에세이로 재구성됨 — Batch(lexical) + 기각과 Blocker 채택은 별개 결정이니 혼동하지 말 것.) 4. 왜 Context가 없는가 — 얕은 버전(코루틴 키 weak table push-pop)조차 quad가 정상 패턴으로 확정한 Slot 비동기 추가에서 조용히 깨지는 이유, 완전 자동 버전이 Roblox Luau의 플랫폼 한계(thread-local 없음)로 불가한 이유, 명시적 타입 강제 Store 전달이 Context보다 안전한 이유(레이어드 Store 대안도 왜 함께 기각됐는지 포함) +5. 왜 push-invalidate/pull-recompute 설계가 laziness와 재계산 방지를 + 최우선 목표로 뒀는가 — 위 `심화` 3번(`왜 push-invalidate/pull-recompute + 인가`)을 더 깊게 확장, `Blocker` 같은 파생 프리미티브가 이 목표 위에서 + 왜 자연스럽게 나왔는지까지 포함하는 설계 철학 에세이 **publish 안 하는 것과의 경계**: 세션별 정정 이력, 조사 원자료(Fusion 반응 그래프 BFS 분석, quad2-try 죽은 코드 조사 등)는 quadnomicon에도