docs: Blocker/Effect를 base/additional-primitives.md에서 별개 파일로 재분리

State 작업 시 Effect까지 같이 볼 필요는 없다는 지적(둘은 무관한 프리미티브)과
기존 프리미티브당 1파일 컨벤션(modifier-plan.md, slot-plan.md류)에 맞춰
base/blocker-plan.md, base/effect-plan.md로 재분리. Blocker는 store-semantics.md
교차 참조를 유지, Effect는 독립 파일로 완전히 분리. 전체 상호참조 경로 갱신.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-07 14:45:57 +09:00
parent 5c9d10df66
commit 226490153e
Signed by: qwreey
GPG key ID: D28DB79297A214BD
10 changed files with 107 additions and 96 deletions

View file

@ -34,7 +34,8 @@
| `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정, getter 이름만 남음 |
| `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 |
| `component-composition-plan.md` | 컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, Source가 State를 구조적으로 만족 — modifier/Ref 컴포넌트 경계 통과까지 전부 확정, 남은 건 API 이름뿐. **[2026-08-07 정리]** 폐기된 `StoreSource` 프록시 설계로의 역전 이력은 본문에서 빼고 `archive/store-source-proxy-reversed.md` 포인터로 압축 |
| `additional-primitives.md` | **[2026-08-07 신설]** `Blocker`(여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게, State 마일스톤과 함께 개발)와 `Effect`(leaf 죽음에 확정 정리) — Blocker는 메커니즘+이름 확정, Effect는 Observer와의 관계가 아직 미해결(문서 내 "미해결" 절, `question.md` 0번) |
| `blocker-plan.md` | **[2026-08-07 신설]** `Blocker` — 여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게, State 마일스톤(M3)과 함께 개발. 메커니즘+이름 확정 |
| `effect-plan.md` | **[2026-08-07 신설]** `Effect` — leaf 죽음에 확정 정리, 재실행 개념 없음. Observer와의 관계가 아직 미해결(문서 내 "미해결" 절, `question.md` 0번) |
| `ui-shorthand-plan.md` | **[2026-08-07 `research/`에서 승격]** `UICorner`/`UIPadding`/`UIScale` 인라인 편의 키 — 이름(v1 `Corner`/`PaddingAll`/`Scale`에서 Modifier 필드명과 안 겹치게 `UI` 프리픽스로 확정)·메커니즘(Handler)·패키지 배치(quad-roblox 코어)·store-bind 가능성까지 전부 확정. 이미지 라운드 트릭(`RoundSize`)은 드롭 — `archive/ui-shorthand-roundsize-dropped.md` 참고 |
## `reference/` — 온디맨드 참고 자료 (2026-08-07 신설)
@ -54,7 +55,7 @@
| `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개, 못 고치는 트레이드오프 정리 | 하 — 사용자 검토 후 반영 여부 결정 대기 |
| `additional-primitives-plan.md` | **[2026-08-07 범위 축소]** 확정/기각된 Effect·Blocker·Batch·Context는 `base/additional-primitives.md`·`archive/`로 분리됨 — 이제 **키 기반 동적 컬렉션 재조정**(Fusion `ForPairs`/Vide `indexes()`류에 대응하는 프리미티브가 quad엔 없음) 하나만 다룸 | 상 — 사용자 판단 대기, 착수 전 M0/M1 스코프에 영향 줄 수 있음 |
| `additional-primitives-plan.md` | **[2026-08-07 범위 축소]** 확정/기각된 Effect·Blocker·Batch·Context는 `base/blocker-plan.md`·`base/effect-plan.md`·`archive/`로 분리됨 — 이제 **키 기반 동적 컬렉션 재조정**(Fusion `ForPairs`/Vide `indexes()`류에 대응하는 프리미티브가 quad엔 없음) 하나만 다룸 | 상 — 사용자 판단 대기, 착수 전 M0/M1 스코프에 영향 줄 수 있음 |
| `pre-implementation-audit.md` | M0 착수 직전 크리티컬 감사(2026-08-06 신설) — `base/` 전체를 모호성/지연결정리스크/단순화후보 세 렌즈로 재검토, 11개 우선순위1(M0~M4 착수 전 확인 권장) + 11개 우선순위2 + 2개 단순화후보 | 상 — M0 착수 전 최소 우선순위1 항목 확인 권장 |
| `v1-compat-plan.md` | v1 하위호환(compat) 레이어 — `quad-roblox-v1-compat` 패키지, v2→v1 단방향 브리지(`state:Observer()`+v1 프로퍼티 재대입), v2-in-v1/v1-in-v2 두 임베딩 방향의 기술 규칙까지 확정. quad2-try의 `quad-compat`은 빈 폴더로 실제 시도된 적 없었음을 확인 | 하 — Slot이 foreign Instance를 어떻게 다루는지만 Slot 코어 구현 시점까지 미결 |
@ -65,7 +66,7 @@
| `store-source-proxy-reversed.md` | [역전됨] 2026-08-04에 확정했던 `StoreSource` 프록시 설계(Store가 Source를 감춘 별도 프록시로 감쌈) — 2026-08-06 세 번째 세션에서 "Source가 State를 구조적으로 만족" 재구성으로 완전히 대체됨. 원문·역전 이유·신구 비교표 보존, `quadnomicon` 소재 후보 |
| `ref-phase-option-reversed.md` | [역전됨] `CreatedRef``phase` 옵션 — 위치 기반 순서 + `PreRef` 신설로 대체됨 |
| `ui-shorthand-roundsize-dropped.md` | **[기각됨, 2026-08-07 신설]** v1 `RoundSize`(이미지 9-slice 라운드 트릭) — 네이티브 `UICorner`로 대체되어 포팅 불필요. 이 판단이 한 차례 "Corner/PaddingAll/Scale 숏핸드 전체가 불필요하다"로 과잉일반화됐다가 정정된 이력 포함 |
| `batch-rejected.md` | **[기각됨, 2026-08-07 신설]** lexical `Batch(fn)` — 코루틴 yield 위에서 구조적으로 위험해 기각, 값 기반 `Blocker`(`base/additional-primitives.md`)로 대체 |
| `batch-rejected.md` | **[기각됨, 2026-08-07 신설]** lexical `Batch(fn)` — 코루틴 yield 위에서 구조적으로 위험해 기각, 값 기반 `Blocker`(`base/blocker-plan.md`)로 대체 |
| `context-rejected.md` | **[기각됨, 2026-08-07 신설]** `Context`(트리 하위 암묵 전파) + 대안이던 레이어드 Store 둘 다 기각 — 명시적 타입 강제 Store 전달로 충분하다는 판단 |
## 참고

View file

@ -1,7 +1,7 @@
# [기각됨] `Batch(fn)` — lexical block 기반 지연/합치기, `Blocker`로 대체됨
**기각 일시**: 2026-08-06~07. **현재 유효한 설계**: `base/
additional-primitives.md`의 "Blocker" 절 — 이 문서가 다루는 것과 같은
blocker-plan.md` — 이 문서가 다루는 것과 같은
문제("여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게")를
값 기반으로 풀어 대체함. 이 파일은 더 이상 능동적으로 참고할 필요 없음
(구현에 안 씀) — "왜 lexical Batch를 기각하고 값 기반 Blocker를 택했는가"가

View file

@ -1,12 +1,12 @@
# 추가 확정 프리미티브 — Blocker / Effect
# Blocker — 여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게
**상태**: base — `research/additional-primitives-plan.md`(다른 프레임워크 대비
갭 분석)에서 갈라져 나온 두 확정 프리미티브. Batch(lexical block)/Context는
기각되어 각각 `archive/batch-rejected.md`/`archive/context-rejected.md`로,
아직 미확정인 키 기반 동적 컬렉션 재조정은 `research/additional-primitives-plan.md`
그대로 남아있음 — 이 문서는 **확정된 것만** 다룬다.
## Blocker — 여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게
**상태**: base — `research/additional-primitives-plan.md`(다른 프레임워크
대비 갭 분석)에서 갈라져 나온 확정 프리미티브. lexical `Batch(fn)`으로
풀려던 대안은 기각되어 `archive/batch-rejected.md`로 분리됨 — 이 문서는
**확정된 Blocker만** 다룬다. `base/effect-plan.md`(같은 조사에서 나온
다른 확정 프리미티브)와는 서로 무관 — Blocker는 State/Store 작업과
밀접히 얽혀 있고 Effect는 완전히 독립된 요소라 원래도 별개 파일이었어야
했음(2026-08-07 문서 정리에서 한 파일로 합쳤던 걸 다시 분리).
**왜 필요한가**: `state1, state2 -> state3`처럼 여러 소스가 한 파생값에
합류할 때, 둘을 한 번에 바꾸면 소비자에게 두 번 전파(재계산+재대입)되는
@ -22,7 +22,7 @@
bind-system-plan.md` "전파 모델 확정" 절)을 전제로 함. 별도 파일로 두되
State와 같은 마일스톤(`ROADMAP.md` M3)에서 함께 구현할 것.
### 메커니즘 (확정)
## 메커니즘 (확정)
```
Blocker() -> blocker -- 생성자
@ -47,7 +47,7 @@ gated state의 동작:
누군가 명시적으로 `:Get()`하면 그 순간의 실제 값을 정상적으로 계산해서
준다 — `store-semantics.md`의 "`Get()`은 라이브 레퍼런스를 준다" 원칙과 일치.
### 사용 예시
## 사용 예시
`state1`/`state2` 각각이 아니라 **결합된 결과(`state3`) 하나에만** `:Block`
건다:
@ -67,7 +67,7 @@ blocker:Off() -- onunblock 핸들 실행 → HasBlockedEmit 확인 → 딱 한
가까운 지점)에 거는 게 원칙 — 소스가 여러 개든, 하나가 한 주기에 여러 번
바뀌든 상관없이 이 지점 하나만 지키면 됨. 소스 쪽에 각각 거는 게 아니다.
### 이름 확정
## 이름 확정
- 클래스: `Blocker``Observer`/`Modifier`/`Ref`와 같은 명사-행위자
네이밍 관례와 일치.
@ -80,7 +80,7 @@ blocker:Off() -- onunblock 핸들 실행 → HasBlockedEmit 확인 → 딱 한
(gated state의 대기 플래그, `Is`/`Has` 접두어로 불리언임을 바로 알려줌).
- 메소드: `state:Block(blocker) -> state`.
### 재진입(네스팅) — 의도적으로 미지원, 강한 문서화 필수
## 재진입(네스팅) — 의도적으로 미지원, 강한 문서화 필수
`IsBlocked`는 카운터가 아니라 단순 불리언이고, **의도적으로 그렇게 둔다.**
레퍼런스 카운팅으로 네스팅을 지원할 수도 있었지만, 그러면 "`On()` 여러
@ -94,64 +94,7 @@ blocker:Off() -- onunblock 핸들 실행 → HasBlockedEmit 확인 → 딱 한
문서(API 레퍼런스 수준)에 명시적으로 강조할 것** — 네스팅을 시도하면
조용히 잘못된 시점에 조기 해제되는, 원인 추적이 어려운 버그로 이어짐.
### 상태: 핵심 메커니즘+이름 확정. 남은 건 문서화뿐
## 상태: 핵심 메커니즘+이름 확정. 남은 건 문서화뿐
`quadnomicon`에서 "Batch를 기각하고 왜 Blocker로 갔는가"를 비교 설명하는
게 좋은 소재(`archive/batch-rejected.md`와 나란히 인용).
---
## Effect — leaf 죽음에 확정 정리, 재실행 개념 없음
**Observer와는 별개의, 완전히 새로운 요소로 확정** — Ref/PreRef 같은
서로 파생된 관계가 아니라 독립적으로 존재하는 primitive. Roblox엔
`task.spawn`으로 코루틴에 반복문/타이머를 돌리는 패턴이 흔하고, Luau
테이블엔 `__gc` 같은 GC 시점 훅이 없어서 "이게 진짜 사라지는 순간"을 아는
유일한 방법은 `Instance.Destroying`류 명시적 신호뿐 — 이런 케이스(타이머
시작 → leaf가 죽을 때 반드시 정지)를 위한 별도 primitive로 합의됨.
```
Effect(fn) -> EffectHandle -- fn을 즉시 1회 실행, 리턴값(nil | () -> ())은
-- 이 Effect가 바인드된 leaf가 죽을 때 정확히 1회 호출
```
**재실행 개념이 없다** — 값 변화에 반응해 다시 도는 건 Observer(+클로저로
직접 짠 cleanup)의 영역이고, Effect는 순수하게 "설치 + 확정 정리" 페어
하나만 담당한다. children 배열에 leaf로 놓는 기존 Observer 바인딩 패턴을
그대로 재사용(그 leaf가 살아있는 동안만 유효, leaf가 죽으면 정리 콜백
호출). 비용은 leaf당 실제 Destroying 바인딩 하나(공유 weak table로 되는
Observer보다 비쌈) — 필요할 때만 쓰는 걸로 충분.
**Observer에 cleanup 반환 계약을 추가하는 안은 기각됨** — React `useEffect`류로
`fn``nil | () -> ()`를 반환하면 다음 재실행 직전에 그걸 불러주는 안을
검토했으나, 클로저 업밸류로 이미 쉽게 되고 잘 작동해서(`local lastConn;
state:Observer(function() if lastConn then lastConn:Disconnect() end;
lastConn = ... end)`) 프레임워크가 이걸 대신해줄 이유가 약하다는 판단 —
`state:Observer(fn)` 자체는 여전히 재실행 계약만 갖고, Effect가 별도로
"1회 설치 + 확정 정리"를 담당하는 이 분리 구조가 유지됨.
### ⚠️ 미해결 — Effect와 Observer의 관계, 사용자 확인 필요
**임의로 결론내지 않고 열어둠(2026-08-07 문서 정리 세션)**: 위 스펙은
`Effect(fn)`를 State에 종속되지 않는 완전한 자유 함수로 서술하지만, 아래
두 가지가 문서상 명확히 확인되지 않음:
1. **`Effect`가 실제로는 `state:Effect(fn)`처럼 State의 메소드(=Observer의
변형, "재실행 없음 + 확정 정리 추가"만 다른 버전)로 구현/노출되어야
하는 것 아닌가?** — 그렇다면 "독립 존재 가능한 프리미티브 vs 원천에
종속된 파생 데이터"(`base/store-semantics.md`) 분류상 Effect도
Observer처럼 후자(자유 함수 생성자 없음, 항상 `:` 메소드)로 재분류해야
함. 지금 이 문서는 이전 조사(`research/additional-primitives-plan.md`)를
따라 자유 함수로 서술했지만, 이게 최종 확정인지는 불명확.
2. **`state:Observer(fn)`가 생성 시점에 `fn`을 즉시 1회 실행하는지가 문서
어디에도 명시돼 있지 않음** — `base/bind-system-plan.md`의 Observer
절은 "값을 안 실어줌, `fn` 본문에서 `Get()`을 다시 읽어야 함"만
명시할 뿐 "생성 즉시 1회 호출되는지"는 다루지 않는다. Effect는
"즉시 1회 실행"이 스펙에 명시돼 있어 이 부분만 보면 둘이 겹쳐
보인다.
이 두 질문이 풀리면 Effect가 (a) 완전히 별개인 자유 함수 primitive로
남는지, (b) `state:Effect(fn)`로 Observer 계열에 합류하는지가 갈린다.
**확인 전까지는 위에 적은 자유 함수 스펙을 잠정 스펙으로 두되, 구현
착수(M3~M4 전후) 전에 반드시 사용자와 다시 확인할 것** — `.claude/
question.md`에 같은 항목 등재됨.

View file

@ -0,0 +1,60 @@
# Effect — leaf 죽음에 확정 정리, 재실행 개념 없음
**상태**: base — `research/additional-primitives-plan.md`(다른 프레임워크
대비 갭 분석)에서 갈라져 나온 확정 프리미티브. `base/blocker-plan.md`(같은
조사에서 나온 다른 확정 프리미티브)와는 서로 무관 — Effect는 Store/State
작업이나 Ref/PreRef와도 파생 관계가 아닌 완전히 독립된 요소라 별도 파일로
둔다(2026-08-07 문서 정리에서 한 파일로 합쳤던 걸 다시 분리).
**Observer와는 별개의, 완전히 새로운 요소로 확정** — Ref/PreRef 같은
서로 파생된 관계가 아니라 독립적으로 존재하는 primitive. Roblox엔
`task.spawn`으로 코루틴에 반복문/타이머를 돌리는 패턴이 흔하고, Luau
테이블엔 `__gc` 같은 GC 시점 훅이 없어서 "이게 진짜 사라지는 순간"을 아는
유일한 방법은 `Instance.Destroying`류 명시적 신호뿐 — 이런 케이스(타이머
시작 → leaf가 죽을 때 반드시 정지)를 위한 별도 primitive로 합의됨.
```
Effect(fn) -> EffectHandle -- fn을 즉시 1회 실행, 리턴값(nil | () -> ())은
-- 이 Effect가 바인드된 leaf가 죽을 때 정확히 1회 호출
```
**재실행 개념이 없다** — 값 변화에 반응해 다시 도는 건 Observer(+클로저로
직접 짠 cleanup)의 영역이고, Effect는 순수하게 "설치 + 확정 정리" 페어
하나만 담당한다. children 배열에 leaf로 놓는 기존 Observer 바인딩 패턴을
그대로 재사용(그 leaf가 살아있는 동안만 유효, leaf가 죽으면 정리 콜백
호출). 비용은 leaf당 실제 Destroying 바인딩 하나(공유 weak table로 되는
Observer보다 비쌈) — 필요할 때만 쓰는 걸로 충분.
**Observer에 cleanup 반환 계약을 추가하는 안은 기각됨** — React `useEffect`류로
`fn``nil | () -> ()`를 반환하면 다음 재실행 직전에 그걸 불러주는 안을
검토했으나, 클로저 업밸류로 이미 쉽게 되고 잘 작동해서(`local lastConn;
state:Observer(function() if lastConn then lastConn:Disconnect() end;
lastConn = ... end)`) 프레임워크가 이걸 대신해줄 이유가 약하다는 판단 —
`state:Observer(fn)` 자체는 여전히 재실행 계약만 갖고, Effect가 별도로
"1회 설치 + 확정 정리"를 담당하는 이 분리 구조가 유지됨.
## ⚠️ 미해결 — Effect와 Observer의 관계, 사용자 확인 필요
**임의로 결론내지 않고 열어둠(2026-08-07 문서 정리 세션)**: 위 스펙은
`Effect(fn)`를 State에 종속되지 않는 완전한 자유 함수로 서술하지만, 아래
두 가지가 문서상 명확히 확인되지 않음:
1. **`Effect`가 실제로는 `state:Effect(fn)`처럼 State의 메소드(=Observer의
변형, "재실행 없음 + 확정 정리 추가"만 다른 버전)로 구현/노출되어야
하는 것 아닌가?** — 그렇다면 "독립 존재 가능한 프리미티브 vs 원천에
종속된 파생 데이터"(`base/store-semantics.md`) 분류상 Effect도
Observer처럼 후자(자유 함수 생성자 없음, 항상 `:` 메소드)로 재분류해야
함. 지금 이 문서는 이전 조사(`research/additional-primitives-plan.md`)를
따라 자유 함수로 서술했지만, 이게 최종 확정인지는 불명확.
2. **`state:Observer(fn)`가 생성 시점에 `fn`을 즉시 1회 실행하는지가 문서
어디에도 명시돼 있지 않음** — `base/bind-system-plan.md`의 Observer
절은 "값을 안 실어줌, `fn` 본문에서 `Get()`을 다시 읽어야 함"만
명시할 뿐 "생성 즉시 1회 호출되는지"는 다루지 않는다. Effect는
"즉시 1회 실행"이 스펙에 명시돼 있어 이 부분만 보면 둘이 겹쳐
보인다.
이 두 질문이 풀리면 Effect가 (a) 완전히 별개인 자유 함수 primitive로
남는지, (b) `state:Effect(fn)`로 Observer 계열에 합류하는지가 갈린다.
**확인 전까지는 위에 적은 자유 함수 스펙을 잠정 스펙으로 두되, 구현
착수(M3~M4 전후) 전에 반드시 사용자와 다시 확인할 것** — `.claude/
question.md`에 같은 항목 등재됨.

View file

@ -296,7 +296,7 @@ State 핸들로 넘기고 `:Get()`을 실제로 읽을 때만 계산)은 `base/b
`Blocker` 참고.** 위 `:With`+`:Compute`만으로는 "state1, state2를 연달아
Set하면 결합된 파생값이 두 번 재계산/재대입된다"는 문제(즉시 pull하는
store-bind 소비자 기준)는 안 풀림 — 이건 별도 확정 프리미티브
`base/additional-primitives.md`의 "Blocker" 절이 다룸(State 개발과 같은
`base/blocker-plan.md`가 다룸(State 개발과 같은
마일스톤, `ROADMAP.md` M3에서 함께 구현). lexical `Batch(fn)`으로 풀려던
초기 시도는 코루틴 yield 위에서 구조적으로 위험해 기각됨 —
`archive/batch-rejected.md` 참고.

View file

@ -16,7 +16,7 @@
같나요. 이것만으로 이 프로젝트는 충분하다 생각해요?" — 여러 서브에이전트
조사 + 사용자와 라이브 논의로 계속 수렴 중. **2026-08-07 문서 정리에서
확정/기각된 항목은 `research/additional-primitives-plan.md`에서
분리됨**: Effect/Blocker → `base/additional-primitives.md`, Batch →
분리됨**: Blocker → `base/blocker-plan.md`, Effect → `base/effect-plan.md`, Batch →
`archive/batch-rejected.md`, Context(+레이어드 Store) → `archive/
context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만** 남김.
@ -32,14 +32,14 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
상세는 `research/additional-primitives-plan.md`(이제 이 주제 전용).
- **Effect가 Observer의 변형(`state:Effect()`)인지, 완전히 독립된 free
function인지 — 신규, 2026-08-07 문서 정리 세션에서 발견.** `base/
additional-primitives.md`가 지금까지의 조사대로 Effect를 "재실행 없는
effect-plan.md`가 지금까지의 조사대로 Effect를 "재실행 없는
독립 free function"으로 서술해뒀지만, 사용자가 직접 `state:Effect()`
형태(=Observer에 "확정 정리" 계약만 추가된 변형)로 기억하고 있어서
확인이 필요함. 관련 하위 질문: `state:Observer(fn)`가 생성 시점에
`fn`을 즉시 1회 실행하는지도 현재 문서 어디에도 명시돼 있지 않음(Effect는
"즉시 1회 실행"이 스펙에 있음 — 이 부분만 보면 둘이 겹쳐 보이는 이유).
**임의로 결론내지 않고 열어둠** — 구현 착수(M3~M4 전후) 전에 확인 필요,
상세는 `base/additional-primitives.md`의 "미해결" 절.
상세는 `base/effect-plan.md`의 "미해결" 절.
- Untrack/Suspense/Error Boundary/Readonly는 조사 결과 새 프리미티브 없이
기존 설계·Lua 자체 기능으로 이미 충분한 것으로 판단(`research/
additional-primitives-plan.md` "빈 자리 아닌 것" 절).
@ -200,7 +200,8 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
| Modifier(정적 merge, immutable 체이닝, State 필드 지원) | `base/modifier-plan.md` |
| 컴포넌트화(플레인 함수, State/Source 경계, 컴포넌트 경계 modifier/Ref는 named parameter로 전달, multi-root 개념 폐기, `Modifier.Merge`) | `base/component-composition-plan.md` |
| 컴포넌트 이식성(전역 store 참조 시 재사용성 문제) | `base/purity-and-effects-plan.md` |
| Blocker(값 기반 emit 지연/합치기), Effect(leaf 죽음에 확정 정리 — 단 Observer와의 관계는 위 0번 열린 질문 참고) | `base/additional-primitives.md` |
| Blocker(값 기반 emit 지연/합치기) | `base/blocker-plan.md` |
| Effect(leaf 죽음에 확정 정리 — 단 Observer와의 관계는 위 0번 열린 질문 참고) | `base/effect-plan.md` |
| UICorner/UIPadding/UIScale 인라인 편의 키 — 이름·메커니즘·store-bind 가능성까지 확정 | `base/ui-shorthand-plan.md` |
| Batch(lexical) 기각, Context(+레이어드 Store) 기각 | `archive/batch-rejected.md`, `archive/context-rejected.md` |
| Fusion/Vide 비교 리서치(주의: 일부 서술은 이후 라운드에서 뒤집힘, 문서 내 정정 표시 참고) | `reference/comparison-fusion-vide.md` |

View file

@ -2,7 +2,7 @@
**상태**: research — 사용자와 라이브 논의로 대부분 수렴(2026-08-06~07),
**2026-08-07 문서 정리에서 확정/기각된 항목을 분리**: Effect/Blocker →
`base/additional-primitives.md`, Batch(lexical) → `archive/
`base/blocker-plan.md`/`base/effect-plan.md`, Batch(lexical) → `archive/
batch-rejected.md`, Context(+레이어드 Store 대안) → `archive/
context-rejected.md`. 이 문서에는 **아직 완전히 열려있는 것 하나만** 남음
— 키 기반 동적 컬렉션 재조정. 사용자가 "작업 전에 모든 정의를 마치고
@ -30,11 +30,11 @@ Vide/v1/artworks 소스 근거 조사, Context 구현 난이도 판정) + 그
| 후보 | 판정 | 현재 위치 |
|---|---|---|
| 키 기반 동적 컬렉션 재조정 | **진짜 빈 자리, 최우선** — 아직 열려있음 | 이 문서(아래) |
| Effect(leaf 죽음에 확정 정리) | 채택, 단 Observer와의 관계는 미해결 | `base/additional-primitives.md` |
| Blocker(값 기반 emit 지연/합치기) | **채택** — Batch의 대안 | `base/additional-primitives.md` |
| Effect(leaf 죽음에 확정 정리) | 채택, 단 Observer와의 관계는 미해결 | `base/effect-plan.md` |
| Blocker(값 기반 emit 지연/합치기) | **채택** — Batch의 대안 | `base/blocker-plan.md` |
| Batch(함수/코루틴 스코프 lexical block) | **기각** | `archive/batch-rejected.md` |
| Context(트리 하위 암묵 전파) + 레이어드 Store | **기각** | `archive/context-rejected.md` |
| Observer에 cleanup 반환 계약 추가 | **기각** — 클로저 업밸류로 이미 충분 | `base/additional-primitives.md`(Effect 절에 근거만 인용) |
| Observer에 cleanup 반환 계약 추가 | **기각** — 클로저 업밸류로 이미 충분 | `base/effect-plan.md`(근거만 인용) |
| Untrack/Peek | 빈 자리 아님 | 아래 "빈 자리 아닌 것" 절 |
| Suspense/비동기 경계 | 빈 자리 아님(문서화 문제로 재분류) | 아래 "빈 자리 아닌 것" 절 |
| Error Boundary | 빈 자리 아님 | 아래 "빈 자리 아닌 것" 절 |
@ -189,7 +189,7 @@ Slot:Add(element, index?) -- 기존 그대로, Extract로 뺀 것도 다시
- **quadnomicon 에세이**:
- "왜 lexical Batch를 기각하고 대신 값 기반 Blocker를 택했는가" —
`archive/batch-rejected.md``base/additional-primitives.md`의
`archive/batch-rejected.md``base/blocker-plan.md`의
Blocker 절을 나란히 비교.
- "왜 Context가 없는가" — `archive/context-rejected.md` 참고.
- "왜 push-invalidate/pull-recompute 설계가 laziness와 재계산 방지를
@ -204,7 +204,7 @@ Slot:Add(element, index?) -- 기존 그대로, Extract로 뺀 것도 다시
유연한 구조" — 명시적 의존성 선언(`:With`) 위에서도 실제 계산은
조건부로 일부만 쓸 수 있다는 팁.
- "Blocker 사용 가이드" — 파이프라인 최종 연산 지점에 배치, **네스팅
금지를 강하게 명시**(`base/additional-primitives.md`의 "재진입" 절
금지를 강하게 명시**(`base/blocker-plan.md`의 "재진입" 절
참고, 문서화 시 최우선 강조 항목).
- "여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산을 피하는
파이프라인/업데이트 순서 팁"(Blocker를 안 쓰는 단순 케이스용 보조 팁,

View file

@ -135,7 +135,7 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부
실제 일어나는 derived state)에 배치하는 게 원칙이라는 것, **네스팅
금지를 최우선으로 강조**(겹치는 배치는 각자 새 `Blocker`를 만들 것 —
안 지키면 조용히 잘못된 시점에 조기 해제되는 원인 추적 어려운 버그로
이어짐) — `base/additional-primitives.md`의 "Blocker" 절
이어짐) — `base/blocker-plan.md`
19. 여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산/재대입을
피하는 파이프라인/업데이트 순서 팁(Blocker를 안 쓰는 단순 케이스용
보조 팁) — `research/additional-primitives-plan.md` "문서화 백로그" 절
@ -205,7 +205,7 @@ additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**:
후보만 있음, `Slot:Extract` 세부 시맨틱도 미정) — `research/
additional-primitives-plan.md`(2026-08-06 신설, 설계 진행 중)
- Effect가 `state:Effect()`로 Observer를 확장하는 형태인지, 완전히 독립된
free function인지 (`base/additional-primitives.md`의 "미해결" 절)
free function인지 (`base/effect-plan.md`의 "미해결" 절)
이 항목들은 `.claude/question.md`에도 이미 열린 질문으로 잡혀있음 — 여기선
"확정 전엔 문서화 대상 아님"이라는 표시만 겸함.

View file

@ -712,13 +712,19 @@ M7 착수 시 `modifier-plan.md` 8번 참고하면 됨.
표면 없이 기존 per-instance weak-table 유틸(`base.perInstanceState`)
재사용만으로 충분하다는 점을 추가.
4. **`additional-primitives-plan.md`를 4갈래로 분리**: 확정된 `Blocker`/
`Effect`는 새 `base/additional-primitives.md`로 승격(Blocker는
State와 같은 마일스톤에서 개발하기로 해서 `store-semantics.md`
교차 참조 추가, `ROADMAP.md` M3에도 체크박스 반영). 기각된 `Batch`
(lexical block)와 `Context`(+대안이던 레이어드 Store)는 각각
`archive/batch-rejected.md`/`archive/context-rejected.md`로 분리.
`research/additional-primitives-plan.md`엔 아직 실제로 열려있는
것(키 기반 동적 컬렉션 재조정) 하나만 남김.
`Effect`는 각각 새 `base/blocker-plan.md`/`base/effect-plan.md`로
승격(Blocker는 State와 같은 마일스톤에서 개발하기로 해서
`store-semantics.md`에 교차 참조 추가, `ROADMAP.md` M3에도 체크박스
반영). 기각된 `Batch`(lexical block)와 `Context`(+대안이던 레이어드
Store)는 각각 `archive/batch-rejected.md`/`archive/context-rejected.md`로
분리. `research/additional-primitives-plan.md`엔 아직 실제로 열려있는
것(키 기반 동적 컬렉션 재조정) 하나만 남김. **[같은 날 바로 정정]**
처음엔 Blocker/Effect를 `base/additional-primitives.md` 한 파일로
합쳐 승격했으나, 사용자가 "State 볼 때 Effect까지 볼 필요는 없다,
기존 프리미티브당 1파일 컨벤션(`modifier-plan.md`/`slot-plan.md`류)에
맞지 않는다"고 지적해 바로 두 파일로 재분리함 — Blocker는
Store/State와 밀접해 교차 참조가 필요하지만 Effect는 완전히 독립된
요소라 애초에 같은 파일일 이유가 없었음.
5. **archive 제목 컨벤션을 둘로 분화** — 기존 `[역전됨]`(한 번 확정했다가
뒤집힌 것, `store-source-proxy-reversed.md`/`ref-phase-option-reversed.md`)과
새로 생긴 `[기각됨]`(확정한 적 없이 후보였다가 채택 안 된 것,
@ -740,7 +746,7 @@ M7 착수 시 `modifier-plan.md` 8번 참고하면 됨.
명시). 관련 하위 질문으로 `state:Observer(fn)`가 생성 시 `fn`을 즉시
1회 실행하는지도 문서 어디에도 명시돼 있지 않음이 이번에 드러남(Effect는
"즉시 1회 실행"이 스펙에 명시돼 있어 이 부분만 보면 둘이 겹쳐 보임).
`base/additional-primitives.md`의 "미해결" 절과 `.claude/question.md`
`base/effect-plan.md`의 "미해결" 절과 `.claude/question.md`
0번에 반영 — 구현 착수(M3~M4 전후) 전에 반드시 재확인할 것.
**다음 세션이 할 일**: 안 바뀜(위 2026-08-06 네 번째 세션 절 "다음 세션이

View file

@ -64,7 +64,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
- [ ] `Source.luau`/`State.luau`/`Store.luau`
- [ ] `store.key` dot-access 타입 추론 확인
- [ ] `Blocker.luau`(`base/additional-primitives.md` 참고 — 여러 Source를
- [ ] `Blocker.luau`(`base/blocker-plan.md` 참고 — 여러 Source를
한꺼번에 바꿔도 파생값 재계산/재대입이 한 번만 되게 하는 primitive,
State와 밀접히 연관돼 있어 같은 마일스톤에서 개발)
- [ ] mock 대상 테스트