diff --git a/.claude/README.md b/.claude/README.md index 99659a1..2cbd6a9 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -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 전달로 충분하다는 판단 | ## 참고 diff --git a/.claude/archive/batch-rejected.md b/.claude/archive/batch-rejected.md index 52a52f5..d0ada8b 100644 --- a/.claude/archive/batch-rejected.md +++ b/.claude/archive/batch-rejected.md @@ -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를 택했는가"가 diff --git a/.claude/base/additional-primitives.md b/.claude/base/blocker-plan.md similarity index 53% rename from .claude/base/additional-primitives.md rename to .claude/base/blocker-plan.md index 4c0d60d..bc98d88 100644 --- a/.claude/base/additional-primitives.md +++ b/.claude/base/blocker-plan.md @@ -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`에 같은 항목 등재됨. diff --git a/.claude/base/effect-plan.md b/.claude/base/effect-plan.md new file mode 100644 index 0000000..e8e7627 --- /dev/null +++ b/.claude/base/effect-plan.md @@ -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`에 같은 항목 등재됨. diff --git a/.claude/base/store-semantics.md b/.claude/base/store-semantics.md index d048320..392ef06 100644 --- a/.claude/base/store-semantics.md +++ b/.claude/base/store-semantics.md @@ -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` 참고. diff --git a/.claude/question.md b/.claude/question.md index 2c54ff4..d5d9e32 100644 --- a/.claude/question.md +++ b/.claude/question.md @@ -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` | diff --git a/.claude/research/additional-primitives-plan.md b/.claude/research/additional-primitives-plan.md index 0a53e2f..e0a9727 100644 --- a/.claude/research/additional-primitives-plan.md +++ b/.claude/research/additional-primitives-plan.md @@ -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를 안 쓰는 단순 케이스용 보조 팁, diff --git a/.claude/research/documentation-content-map.md b/.claude/research/documentation-content-map.md index a5f651b..0be0516 100644 --- a/.claude/research/documentation-content-map.md +++ b/.claude/research/documentation-content-map.md @@ -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`에도 이미 열린 질문으로 잡혀있음 — 여기선 "확정 전엔 문서화 대상 아님"이라는 표시만 겸함. diff --git a/CLAUDE.md b/CLAUDE.md index f33c44d..7f4e13e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 네 번째 세션 절 "다음 세션이 diff --git a/ROADMAP.md b/ROADMAP.md index b2a7bc2..751eaf4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 대상 테스트