6차 라운드 + 컴포넌트화/Modifier 논의, 문서 코퍼스 전체 정리 결과 반영
- 6차 라운드: 태그 네임스페이싱(Ref로 충분), Store가 Store를 담지 않음 확정 - Modifier 메커니즘 전체 확정(정적 merge, immutable+clone 체이닝, State 필드 지원, "관측해야 실체화된다" 전역 원칙) — base/modifier-plan.md 신설 - 컴포넌트화 논의 시작(research/component-composition-plan.md) — 컴포넌트= 플레인 함수, State/Source 읽기·쓰기 경계, StoreSource 프록시까지 수렴, modifier/Ref의 컴포넌트 경계 통과 방식은 열린 채로 남김 - .claude/ 코퍼스 전체(약 15개 문서)를 서브에이전트로 감사해 여러 라운드에 걸쳐 쌓인 모순/중복/stale 마커/끊긴 참조 다수 수정 - purity-and-effects-plan.md를 research/에서 base/로 승격 - CLAUDE.md: 라운드별 인수인계 메모 3개를 하나로 통합, 오래된 "더 이상 열린 질문 없음" 모순 제거 - question.md: 시간순도 우선순위순도 아니던 구조를 "지금 열려있는 것" 중심 으로 재정리, 용어 정리 제안(State/DI/PerInstanceState 등 우선순위) 추가 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c00e2e67d6
commit
c19e82f661
16 changed files with 585 additions and 382 deletions
|
|
@ -14,7 +14,7 @@
|
||||||
| `qa-request/` | 구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음 — 지금은 구현 자체가 시작 전이라 비어있음 |
|
| `qa-request/` | 구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음 — 지금은 구현 자체가 시작 전이라 비어있음 |
|
||||||
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 — 지금은 비어있음 |
|
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 — 지금은 비어있음 |
|
||||||
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — 지금은 비어있음(구현 시작 전) |
|
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — 지금은 비어있음(구현 시작 전) |
|
||||||
| `initreq/` | 프로젝트 착수 시 클론해둔 참고 레포(quad v1, fusion, vide, rbvm, tbox, code-docker) + 원본 요청(`req.md`, `raw-userinput.md`) + `quad2-try`(이전에 시도했다 폐기한 v2 재작성 시도 — 리서치 완료, 결론은 `base/bind-system-plan.md`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
|
| `initreq/` | 프로젝트 착수 시 클론해둔 참고 레포(quad v1, fusion, vide, rbvm, tbox, code-docker) + PA님 실 코드(`artworks/`, 4차 라운드 교차검증 근거) + 원본 요청(`req.md`, `raw-userinput.md`) + `quad2-try`(이전에 시도했다 폐기한 v2 재작성 시도 — 리서치 완료, 결론은 `base/bind-system-plan.md`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
|
||||||
|
|
||||||
`research/`의 문서가 설계 확정되면 `base/`로 승격(또는 구현 착수 시
|
`research/`의 문서가 설계 확정되면 `base/`로 승격(또는 구현 착수 시
|
||||||
`qa-request/`행). 지금은 구현 라운드 전(설계 단계)이라 전부 `base/`/`research/`에만
|
`qa-request/`행). 지금은 구현 라운드 전(설계 단계)이라 전부 `base/`/`research/`에만
|
||||||
|
|
@ -26,20 +26,22 @@
|
||||||
|---|---|
|
|---|---|
|
||||||
| `architecture.md` | quad-v2 전체 아키텍처 확정 사항 요약(제일 먼저 볼 문서) |
|
| `architecture.md` | quad-v2 전체 아키텍처 확정 사항 요약(제일 먼저 볼 문서) |
|
||||||
| `quad-v1-architecture.md` | v1(`initreq/quad`) 내부 동작 스냅샷 — "이 문제를 안 반복하려면"의 기준선 |
|
| `quad-v1-architecture.md` | v1(`initreq/quad`) 내부 동작 스냅샷 — "이 문제를 안 반복하려면"의 기준선 |
|
||||||
| `comparison-fusion-vide.md` | Fusion/Vide 아키텍처 비교 리서치 — 설계 결정 근거 자료 |
|
| `comparison-fusion-vide.md` | Fusion/Vide 아키텍처 비교 리서치 — 설계 결정 근거 자료(전파 모델 등 일부 서술은 이후 라운드에서 뒤집혔으니 `bind-system-plan.md` 쪽을 최신으로 볼 것) |
|
||||||
| `lifecycle-pattern.md` | rbvm의 `Connected`+GC 관용구를 quad-v2가 채택하는 방식 |
|
| `lifecycle-pattern.md` | rbvm의 `Connected`+GC 관용구를 quad-v2가 채택하는 방식 |
|
||||||
| `store-semantics.md` | Store는 부작용 허용이 기본. State는 Store 위의 조합 가능한 캐시 레이어로 실제로 필요함(2026-08-04 정정) — 온톨로지 핵심 메커니즘은 2026-08-04 2차 라운드에서 확정, 최신 상세는 `base/bind-system-plan.md` |
|
| `store-semantics.md` | Store는 부작용 허용이 기본. State는 Store 위의 조합 가능한 캐시 레이어로 실제로 필요함(2026-08-04 정정) — 온톨로지 핵심 메커니즘은 2026-08-04 2차 라운드에서 확정, 최신 상세는 `base/bind-system-plan.md` |
|
||||||
| `bind-system-plan.md` | pluggable key/value 핸들러 레지스트리 — `process`/`retract` 디스패치 모델, Ref, Store/State/Source 온톨로지 + 인체공학 질문 전부 확정. 디스패치 엔진은 `quad-base`가 인터페이스로 소유(2026-08-04 5차 라운드) | — |
|
| `bind-system-plan.md` | pluggable key/value 핸들러 레지스트리 — `process`/`retract` 디스패치 모델, Ref, Store/State/Source 온톨로지 + 인체공학 질문 전부 확정. 디스패치 엔진은 `quad-base`가 인터페이스로 소유(2026-08-04 5차 라운드) |
|
||||||
| `module-lifecycle-plan.md` | 프로바이더 패턴, bind/store 구현 책임 분리 — 확정 | — |
|
| `module-lifecycle-plan.md` | 프로바이더 패턴, bind/store 구현 책임 분리 — 확정 |
|
||||||
| `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정 | — |
|
| `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정 |
|
||||||
|
| `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정, getter 이름만 남음 |
|
||||||
|
| `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 |
|
||||||
|
|
||||||
## `research/` — 아직 착수 전, 상의 필요
|
## `research/` — 아직 착수 전, 상의 필요
|
||||||
|
|
||||||
| 문서 | 내용 | 우선순위 |
|
| 문서 | 내용 | 우선순위 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `tween-plan.md` | 트윈을 Store 밖 특수 bind key로 처리, 기본 오버라이드는 Cancel | 중 — 세부 옵션만 남음 |
|
| `tween-plan.md` | 트윈을 Store 밖 특수 bind key로 처리, 기본 오버라이드는 Cancel | 중 — 세부 옵션만 남음 |
|
||||||
| `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 | 하 — 문서화 성격, 급하지 않음 |
|
|
||||||
| `existing-instance-bind-plan.md` | 이미 생성된 인스턴스 재바인드 — 착수 안 하되 "미지원" 확정도 안 함, 열린 가능성 유지 | 하 — v2 초기 스코프 제외 |
|
| `existing-instance-bind-plan.md` | 이미 생성된 인스턴스 재바인드 — 착수 안 하되 "미지원" 확정도 안 함, 열린 가능성 유지 | 하 — v2 초기 스코프 제외 |
|
||||||
|
| `component-composition-plan.md` | 컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, `StoreSource` 프록시 — 핵심 골격 수렴, modifier/Ref가 컴포넌트 경계를 어떻게 통과하는지만 남음 | 상 — 사용자가 "가장 문제되는 부분"으로 직접 지목 |
|
||||||
|
|
||||||
## 참고
|
## 참고
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,17 +31,36 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo
|
||||||
내장되어 store 컴퓨티드 바인드도 가능해야 함.
|
내장되어 store 컴퓨티드 바인드도 가능해야 함.
|
||||||
5. **id 기반 전역 조회 폐지, Tag 시스템으로 대체.** v1의 `Store.GetObject(id)`/
|
5. **id 기반 전역 조회 폐지, Tag 시스템으로 대체.** v1의 `Store.GetObject(id)`/
|
||||||
`Frame "id" {}`류는 더 이상 없음 — "id 매핑이 비현실적"이라는 게 이유.
|
`Frame "id" {}`류는 더 이상 없음 — "id 매핑이 비현실적"이라는 게 이유.
|
||||||
네임스페이싱 문제는 있지만(`.claude/question.md` 참고) 별도 네임스페이스
|
네임스페이싱 문제는 있지만 별도 네임스페이스 개념을 추가하면 라이브러리
|
||||||
개념을 추가하면 라이브러리 복잡도가 너무 올라간다고 판단 — 당장은
|
복잡도가 너무 올라간다고 판단 — 당장은 TagService 그대로 사용. **대신
|
||||||
TagService 그대로 사용. **대신 Ref가 도입됨** — 단 Ref의 용도는 "id로 조회"가
|
Ref가 도입됨** — 단 Ref의 용도는 "id로 조회"가 아니라 "외부에서 이미
|
||||||
아니라 "외부에서 이미 관리되고 있는 instance를 quad로 점진적으로 마이그레이션/
|
관리되고 있는 instance를 quad로 점진적으로 마이그레이션/래핑하기 위해
|
||||||
래핑하기 위해 직접 참조를 얻는 것"(`base/bind-system-plan.md`의 Ref 절
|
직접 참조를 얻는 것"(`base/bind-system-plan.md`의 Ref 절 참고) — 둘을
|
||||||
참고) — 둘을 혼동하지 말 것.
|
혼동하지 말 것.
|
||||||
|
- **2026-08-04 6차: 네임스페이싱 충돌을 심각하게 안 보는 이유 확정.**
|
||||||
|
충돌을 피해야 하는 단위는 보통 컴포넌트 단위로 나오고, 그 경우는 Ref로
|
||||||
|
직접 참조를 얻으면 되므로 태그 자체의 전역 네임스페이스가 굳이 필요
|
||||||
|
없음. 태그는 원래 주로 스타일링(스타일시트 셀렉터) 용도인데, 스타일시트는
|
||||||
|
적용 위치가 트리 상위에 존재해야 하고 사용자가 직접 그 위치에 심어야
|
||||||
|
하는 등 스크립팅으로 구성하기 어려워 quad 같은 UI 라이브러리에서는 잘
|
||||||
|
안 쓰는 접근 — 그래서 스타일시트 대신 modifier kit을 제공하는 것(아래
|
||||||
|
7번 항목의 modifier 우선순위 규칙 참고).
|
||||||
6. **함수지향 디폴트, `:` 체이닝은 예외적으로만.** 스토어 바인드처럼 체인이 정말
|
6. **함수지향 디폴트, `:` 체이닝은 예외적으로만.** 스토어 바인드처럼 체인이 정말
|
||||||
편한 경우만 `:` 사용, 나머지는 외부 함수가 인스턴스를 인자로 받는 모양.
|
편한 경우만 `:` 사용, 나머지는 외부 함수가 인스턴스를 인자로 받는 모양.
|
||||||
7. **Style(Default) 시스템 폐기.** Roblox 자체 스타일시트를 쓰는 게 낫다고 판단.
|
7. **Style(Default) 시스템 폐기.** 대신 modifier(spread되는 값, `...`으로
|
||||||
대신 modifier(spread되는 값, `...`으로 풀리는 것)를 지향 — 함수형 modifier가
|
풀리는 것)를 지향 — 함수형 modifier가 store 바인드를 받을 수도 있음.
|
||||||
store 바인드를 받을 수도 있음.
|
(초기 근거였던 "Roblox 자체 스타일시트를 쓰는 게 낫다"는 6차 라운드에서
|
||||||
|
갱신됨 — 위 5번 항목의 6차 추가분 참고: 스타일시트는 적용 위치 제약과
|
||||||
|
스크립팅 난이도 때문에 오히려 안 쓰기로 하고 modifier kit으로 대체함.)
|
||||||
|
- **2026-08-04 세션: modifier 메커니즘 전체 확정, 상세는
|
||||||
|
`research/modifier-plan.md`로 분리.** 요지만: 런타임 pluggable 핸들러가
|
||||||
|
아니라 디스패치 이전에 정적으로 flatten되는 값(핸들러 레지스트리 미참여,
|
||||||
|
CSS cascade 문제 회피). Merge 우선순위는 "배열 순서상 나중 modifier가
|
||||||
|
우선"과 "인라인 키는 modifier보다 무조건 우선"이라는 독립된 두 규칙(Lua
|
||||||
|
테이블 리터럴이 배열/해시 파트 간 소스 순서를 보존 안 하므로 하나로 합칠
|
||||||
|
수 없음). 값은 immutable — 체이닝 메소드(`:FontSize(...)`류)는 항상
|
||||||
|
`table.clone` 후 반환, 원본 mutate 금지(형제 서브트리 오염/재렌더 드리프트
|
||||||
|
방지, 비용은 무시 가능한 수준으로 확인됨).
|
||||||
8. **특수 이벤트는 특수 플러깅으로.** `PropertyChangedSignal`, `PropertyChangedEvent ""`
|
8. **특수 이벤트는 특수 플러깅으로.** `PropertyChangedSignal`, `PropertyChangedEvent ""`
|
||||||
같은 것들은 일반 이벤트 바인드가 아니라 pluggable 바인드 핸들러 중 하나로
|
같은 것들은 일반 이벤트 바인드가 아니라 pluggable 바인드 핸들러 중 하나로
|
||||||
구현(`base/bind-system-plan.md`).
|
구현(`base/bind-system-plan.md`).
|
||||||
|
|
@ -59,9 +78,10 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo
|
||||||
확정으로 재확인 — 더 이상 열린 질문 아님.)
|
확정으로 재확인 — 더 이상 열린 질문 아님.)
|
||||||
12. **멀티 타겟(pluggable 백엔드) — 특히 GTK 지원까지 염두.** Roblox 전용 렌더
|
12. **멀티 타겟(pluggable 백엔드) — 특히 GTK 지원까지 염두.** Roblox 전용 렌더
|
||||||
기술(react.lua, Fusion)은 결국 외부 개발자 유인이 없어 발전이 더딜 거라는
|
기술(react.lua, Fusion)은 결국 외부 개발자 유인이 없어 발전이 더딜 거라는
|
||||||
문제의식. 결과적으로 `plug/roblox`, `plug/base` 정도로 나뉠 전망 — base가
|
문제의식. 결과적으로 `quad-base`/`quad-roblox`로 나뉨(5차 라운드에서 확정된
|
||||||
가상돔 없이도 프로바이더 패턴으로 백엔드를 받는 인터페이스만 정의하고,
|
정확한 패키지 이름, 아래 "구현 착수" 절 참고) — base가 가상돔 없이도
|
||||||
실제 Roblox 구현은 `quad-roblox` 격 서브패키지가 담당.
|
프로바이더 패턴으로 백엔드를 받는 인터페이스만 정의하고, 실제 Roblox 구현은
|
||||||
|
`quad-roblox`가 담당.
|
||||||
13. **모듈은 기본 싱글톤, `New()`는 나중에.** 한 Lua 스레드에서 Roblox/비-Roblox
|
13. **모듈은 기본 싱글톤, `New()`는 나중에.** 한 Lua 스레드에서 Roblox/비-Roblox
|
||||||
프로바이더를 동시에 쓸 일이 거의 없을 거라 판단 — 필요해지면 그때 `New()`
|
프로바이더를 동시에 쓸 일이 거의 없을 거라 판단 — 필요해지면 그때 `New()`
|
||||||
추가.
|
추가.
|
||||||
|
|
@ -137,21 +157,22 @@ quad/
|
||||||
Tween/purity/existing-instance-bind는 여전히 `research/`에 남아있고 이
|
Tween/purity/existing-instance-bind는 여전히 `research/`에 남아있고 이
|
||||||
구조 확정을 막지 않음.
|
구조 확정을 막지 않음.
|
||||||
|
|
||||||
## 아직 미정 (research/로 분리됨)
|
## Store/State/Source 온톨로지 — 확정됨 (요약)
|
||||||
|
|
||||||
Tween 플러깅, 순수함수 범위, 이미 생성된 인스턴스에 대한 바인드 —
|
Store는 source(실제 값이 존재하는 단일 지점) 집합체이고, `store.key`로 접근할
|
||||||
`.claude/research/` 각 문서 참고, 전체 색인은 `.claude/README.md`. 바인드
|
때마다 그 source를 감싸는 새 State(자기 고유 value 없는 조합 가능한 캐시)를
|
||||||
디스패치/Slot/모듈 라이프사이클은 위 "구현 착수" 섹션대로 확정되어
|
|
||||||
`.claude/base/`로 승격됨(`bind-system-plan.md`/`module-lifecycle-plan.md`/
|
|
||||||
`slot-plan.md`).
|
|
||||||
|
|
||||||
**Store/State/Source 온톨로지(2026-08-04 두 라운드에 걸쳐 확정)**: Store는
|
|
||||||
source(실제 값이 존재하는 단일 지점) 집합체이고, `store.key`로 접근할 때마다
|
|
||||||
그 source를 감싸는 새 State(자기 고유 value 없는 조합 가능한 캐시)를
|
|
||||||
반환한다. 전파는 push-invalidate(신호만)/pull-recompute(`Get()` 시점) —
|
반환한다. 전파는 push-invalidate(신호만)/pull-recompute(`Get()` 시점) —
|
||||||
Fusion식 eager 노드 없이도 다이아몬드 의존성 중복 재계산 문제가 풀림. State는
|
Fusion식 eager 노드 없이도 다이아몬드 의존성 중복 재계산 문제가 풀림. State는
|
||||||
쓰기 대상이 아니고(값 쓰기는 항상 Store의 `__newindex`), 값 하나만 다룰 땐
|
쓰기 대상이 아니고(값 쓰기는 항상 Store의 `__newindex`), 값 하나만 다룰 땐
|
||||||
Store와 별개인 가벼운 `Source` 프리미티브를 씀. 남은 건 정확한 API 이름과
|
Store와 별개인 가벼운 `Source` 프리미티브를 씀. `store.key` dot-access를 타입
|
||||||
"`store.key` dot-access를 타입 추론 1급 경로로 삼는다"는 제안의 정식 확인
|
추론 1급 경로로 삼는 것도 3차 라운드에서 정식 확정됨 — **더 이상 열린 질문
|
||||||
뿐 — `base/bind-system-plan.md`의 "Store/State/Source 온톨로지" 절,
|
아님**, 남은 건 정확한 API 이름뿐. 상세는 `base/bind-system-plan.md`의
|
||||||
`.claude/question.md` 참고.
|
"Store/State/Source 온톨로지" 절 참고.
|
||||||
|
|
||||||
|
## 아직 미정 (research/로 분리됨)
|
||||||
|
|
||||||
|
Tween 플러깅, 이미 생성된 인스턴스에 대한 바인드, 컴포넌트가 modifier/Ref를
|
||||||
|
경계 너머로 어떻게 전달하는지 — `.claude/research/` 각 문서 참고, 전체 색인은
|
||||||
|
`.claude/README.md`. 바인드 디스패치/Slot/모듈 라이프사이클은 위 "구현 착수"
|
||||||
|
섹션대로 확정되어 `.claude/base/`로 승격됨(`bind-system-plan.md`/
|
||||||
|
`module-lifecycle-plan.md`/`slot-plan.md`).
|
||||||
|
|
|
||||||
|
|
@ -122,10 +122,14 @@ Slot이 store 바인드로 넘어오는 경우, pluggable 처리기에 `retract`
|
||||||
아니면 아예 다른 값으로 둬야 하는가? table/number 같은 프리미티브 타입이나
|
아니면 아예 다른 값으로 둬야 하는가? table/number 같은 프리미티브 타입이나
|
||||||
ref 타입처럼 생각하는 게 맞는 거 같음 — 그걸 처리하는 플러그를 만드는 걸로."
|
ref 타입처럼 생각하는 게 맞는 거 같음 — 그걸 처리하는 플러그를 만드는 걸로."
|
||||||
|
|
||||||
이 문서의 제안: "Store 안의 값이 Store"인 경우도 그냥 하나의 (key,value) 쌍일
|
**2026-08-04 6차 확정: 그런 경우는 없다고 본다.** 위에 적힌 "재실행 래핑으로
|
||||||
뿐이고, 그 값 타입(Store)을 인식하는 핸들러가 pluggable 레지스트리에 등록되어
|
기계적으로는 커버 가능하다"는 제안은 메커니즘상 틀리지 않지만, 실제 설계
|
||||||
있으면 됨 — 위 "재실행 래핑" 방식과 동일한 메커니즘으로 커버됨. 별도 특수
|
의도와 안 맞음 — Store는 Source에 준하는 존재로 모든 반응형 값의 "시작점"
|
||||||
케이스 코드 불필요.
|
역할만 함. 시작점은 다른 변화하는 무언가에 연결되는 것을 제공하고자 하지
|
||||||
|
않음(= Store가 다른 Store/State를 값으로 담아 자동으로 따라가게 하는 용도로
|
||||||
|
쓰지 않음). Store에서 값을 꺼내 State를 옵저빙하다가 콜백으로 다른 Store 값을
|
||||||
|
바꾸는 식의 수동 연결은 있을 수 있지만, 잘 짜인 UI에서 실사용 사례를 거의
|
||||||
|
보지 못했다는 게 사용자 판단 — 그래서 이 케이스를 위해 별도로 신경 쓰지 않음.
|
||||||
|
|
||||||
## Ref — 도입 확정, 단 용도는 재정의됨
|
## Ref — 도입 확정, 단 용도는 재정의됨
|
||||||
|
|
||||||
|
|
@ -167,29 +171,29 @@ ref 타입처럼 생각하는 게 맞는 거 같음 — 그걸 처리하는 플
|
||||||
|
|
||||||
**채택 방향**: `:With(...)`로 필요한 의존성을 모으고, 그 뒤 `:Compute(function()
|
**채택 방향**: `:With(...)`로 필요한 의존성을 모으고, 그 뒤 `:Compute(function()
|
||||||
... end)`에서 **`with`한 값을 포지셔널 인자로 받지 않고 클로저로 직접 읽는다**
|
... end)`에서 **`with`한 값을 포지셔널 인자로 받지 않고 클로저로 직접 읽는다**
|
||||||
(정확히 어떤 방식으로 "직접 읽는지"는 아래 열린 질문 — `:fromState` 후보 참고).
|
(정확히 어떤 방식으로 "직접 읽는지"는 2차 라운드에서 확정 — self/with 값 둘 다
|
||||||
|
lazy State 핸들로 통일, 아래 "Store/State/Source 온톨로지" 절의 "`:With`/
|
||||||
|
`:Compute`" 부분 참고).
|
||||||
|
|
||||||
## Unix 파이프에서 영감 받은 스트림 지향 — 원래 의도, 기술적 난이도 미확정
|
## Unix 파이프에서 영감 받은 스트림 지향 — 원래 의도, 해소됨
|
||||||
|
|
||||||
**중요한 배경**: quad는 원래 이 파이프라인/스트림 개념에서 영감을 받아 만들어짐.
|
**배경**: quad는 원래 이 파이프라인/스트림 개념에서 영감을 받아 만들어짐.
|
||||||
이상적으로는 store에서 한 값을 추적(track)하면 "State"가 나오고, 거기에
|
이상적으로는 store에서 한 값을 추적(track)하면 "State"가 나오고, 거기에
|
||||||
`compute`를 적용하면 또 다른 "State"가 나오는 식 — Unix의 `(cat a; cat b) | while
|
`compute`를 적용하면 또 다른 "State"가 나오는 식 — Unix의 `(cat a; cat b) | while
|
||||||
read ...`처럼, State끼리 자유롭게 합성/파이핑 가능한 것이 최종 목표. `:With`의
|
read ...`처럼, State끼리 자유롭게 합성/파이핑 가능한 것이 최종 목표. `:With`의
|
||||||
두 번째 인자(`b`)도 다른 `:Compute`의 결과물(State)을 그대로 받을 수 있어야
|
두 번째 인자(`b`)도 다른 `:Compute`의 결과물(State)을 그대로 받을 수 있어야
|
||||||
이상적.
|
이상적.
|
||||||
|
|
||||||
**미해결 긴장 관계**: 이걸 구현하는 두 갈래 방식이 있고 어느 쪽이 맞는지 아직
|
**해소됨(2차 라운드) — 두 갈래 방식 중 실질적으로 옵션 2 방향으로 정리됨**:
|
||||||
결정 안 됨:
|
당시엔 (1) Compute 체인이 자기 자신을 mutable하게 바꾸는 방식(엔지니어링 비용
|
||||||
1. **Compute 체인이 항상 자기 자신을 mutable하게 바꾼다** — 엔지니어링 비용은
|
낮지만 공유/합성이 깨짐) vs (2) 명시적 `State:fromState(state)`류 비-mutating
|
||||||
낮지만, 다른 코드가 나중에 그 체인 뒤에 새 compute를 붙이는(다른 소비자가
|
생성자(합성은 안전, 비용 미확정) 둘로 긴장이 있었으나, 실제 확정된 모델은
|
||||||
동일 State에 독립적으로 파생값을 추가하는) 것이 불가능해짐 — 공유/합성이
|
아래 "Store/State/Source 온톨로지" 절의 **`state(state)`로 기존 state의
|
||||||
깨짐.
|
결과를 받아 새 state를 만드는 조합**임 — 매번 새 State를 만든다는 점에서
|
||||||
2. **명시적 `State:fromState(state)`류의 비-mutating 생성자** — 합성은
|
옵션 2와 같은 축(비-mutating)이고, 별도 `fromState`/`Pipe` 콤비네이터 타입
|
||||||
안전해지지만 엔지니어링 비용이 더 큼(정확히 얼마나 큰지 미확정).
|
없이도 `state(state)` 하나로 충분하다는 게 최종 결론(`Pipe` 후보는 폐기).
|
||||||
|
`base/architecture.md`의 "복사 구현 지양, 팩토리 함수로 대체" 원칙과 같은
|
||||||
이건 `base/architecture.md`의 "복사 구현 지양, 팩토리 함수로 대체" 원칙과
|
축의 해법.
|
||||||
같은 축의 문제 — 옵션 2가 그 원칙과 더 잘 맞아 보이지만, 실현 가능성 자체가
|
|
||||||
아직 검증 안 됨.
|
|
||||||
|
|
||||||
## Store/State/Source 온톨로지 — 핵심 메커니즘 확정 (2026-08-04 2차 라운드)
|
## Store/State/Source 온톨로지 — 핵심 메커니즘 확정 (2026-08-04 2차 라운드)
|
||||||
|
|
||||||
|
|
@ -228,6 +232,22 @@ Fusion식 eager 노드·생성순 정렬은 안 만듦**
|
||||||
- `emit`은 이 무효화 신호 하나로 좁혀짐 — 값을 안 실어보내므로 저렴함
|
- `emit`은 이 무효화 신호 하나로 좁혀짐 — 값을 안 실어보내므로 저렴함
|
||||||
("emit 필요 여부" 열린 질문은 이걸로 해소).
|
("emit 필요 여부" 열린 질문은 이걸로 해소).
|
||||||
|
|
||||||
|
**전역 원칙으로 명문화: "관측해야 실체화된다" (2026-08-04 세션)**
|
||||||
|
|
||||||
|
위 pull-recompute 규칙을 State 하나의 재계산 메커니즘으로만 읽지 말고,
|
||||||
|
프로젝트 전역에 적용되는 원칙으로 명시함: **어떤 파생값도 `.value`/`Get()`로
|
||||||
|
직접 읽히기(관측) 전까지는 계산되지 않는다.** 이 원칙은 State 자체뿐 아니라,
|
||||||
|
State를 필드 값으로 담고 있는 다른 구조(예: `base/modifier-plan.md`의
|
||||||
|
Modifier)에도 그대로 적용됨 — Modifier의 getter가 State 필드를 읽으면 그
|
||||||
|
순간이 바로 관측이고, 그 순간 계산이 확정됨.
|
||||||
|
|
||||||
|
**주의 — 구조적 복사는 관측이 아님.** `table.clone`처럼 테이블 레퍼런스만
|
||||||
|
복사하는 연산은 안에 담긴 State 핸들을 그대로 옮길 뿐 `.value`/`Get()`을
|
||||||
|
호출하지 않으므로 관측이 아니고, 계산을 트리거하지 않음. Modifier 체이닝
|
||||||
|
메소드가 `table.clone` 후 필드를 덮어쓰는 것(위 "Immutable 값 + clone 기반
|
||||||
|
체이닝")과 이 원칙이 충돌하지 않는 이유가 바로 이것 — clone은 그저 참조
|
||||||
|
복사라 State 필드는 클론 이후에도 여전히 살아있는 lazy 핸들로 남음.
|
||||||
|
|
||||||
**`:With`/`:Compute` — self 인자도 lazy 핸들로 통일**
|
**`:With`/`:Compute` — self 인자도 lazy 핸들로 통일**
|
||||||
|
|
||||||
- 최초안(self 값은 포지셔널 raw 값, with한 값만 클로저로 읽음)에는 실제
|
- 최초안(self 값은 포지셔널 raw 값, with한 값만 클로저로 읽음)에는 실제
|
||||||
|
|
@ -278,9 +298,10 @@ Fusion식 eager 노드·생성순 정렬은 안 만듦**
|
||||||
레코드 타입으로 지으면 일반 구조적 필드 타이핑으로 자동 해결되고, 문자열
|
레코드 타입으로 지으면 일반 구조적 필드 타이핑으로 자동 해결되고, 문자열
|
||||||
리터럴 narrowing 문제 자체가 안 생김. `store "key"` 문자열 커링은 동적
|
리터럴 narrowing 문제 자체가 안 생김. `store "key"` 문자열 커링은 동적
|
||||||
키가 필요할 때 쓰는 미타입(`State<any>`) 폴백으로 격하.
|
키가 필요할 때 쓰는 미타입(`State<any>`) 폴백으로 격하.
|
||||||
- 이 패턴은 Store에만 국한되지 않고 **인스턴스 생성(`DI.Frame`)/이벤트
|
- 이 패턴은 Store에만 국한되지 않고 **인스턴스 생성까지 관통하는 프로젝트
|
||||||
(`On.EventName`)까지 관통하는 프로젝트 전역 관습으로 확정**됨 — 아래
|
전역 관습으로 확정**됨 — 단 이벤트는 이후 4차 라운드에서 이 관습의
|
||||||
"인스턴스 생성 / 이벤트 네이밍 인체공학" 절 참고.
|
**유일한 예외**로 빠졌음(PA님 방식인 문자열 키+런타임 리플렉션으로 전환).
|
||||||
|
아래 "인스턴스 생성 / 이벤트 네이밍 인체공학" 절이 최신 확정 내용.
|
||||||
|
|
||||||
**`Pipe`(quad2-try 후보)는 폐기 확정** — 별도 `Pipe` 타입에 소유권/버전
|
**`Pipe`(quad2-try 후보)는 폐기 확정** — 별도 `Pipe` 타입에 소유권/버전
|
||||||
가드를 넣어 재설계하는 대신, State 자체가 파이핑 결합체이고
|
가드를 넣어 재설계하는 대신, State 자체가 파이핑 결합체이고
|
||||||
|
|
@ -359,15 +380,16 @@ State/스트림)를 다뤘던 이전 시도가 있었음. 조사 결과 요약:
|
||||||
**건질 만한 것 (인체공학/아이디어만, 코드는 아님):**
|
**건질 만한 것 (인체공학/아이디어만, 코드는 아님):**
|
||||||
- **`store:Pipe(key):Compute(fn)` 같은 왼쪽에서 오른쪽으로 읽히는 파이프
|
- **`store:Pipe(key):Compute(fn)` 같은 왼쪽에서 오른쪽으로 읽히는 파이프
|
||||||
문법 자체**는 목표로 유지할 가치가 있음.
|
문법 자체**는 목표로 유지할 가치가 있음.
|
||||||
- **`Pipe`가 mutate-vs-`fromState` 긴장 관계에 제시한 절충안** — "체이닝된
|
- **`Pipe`가 mutate-vs-`fromState` 긴장 관계에 제시했던 절충안** — "체이닝된
|
||||||
`Compute`/`Add`/... 호출은 자신이 액션 리스트의 유일한 '끝(tip)'일 때만 공유
|
`Compute`/`Add`/... 호출은 자신이 액션 리스트의 유일한 '끝(tip)'일 때만 공유
|
||||||
배열에 그대로 append(뮤테이션), 이미 다른 코드가 그 지점 이후로 체인을
|
배열에 그대로 append(뮤테이션), 이미 다른 코드가 그 지점 이후로 체인을
|
||||||
확장해버렸다면 배열을 복사한 뒤 새 `Pipe` 객체를 반환"하는 **copy-on-write
|
확장해버렸다면 배열을 복사한 뒤 새 `Pipe` 객체를 반환"하는 **copy-on-write
|
||||||
방식** — 이건 이 문서의 "mutate-in-place vs `fromState`" 긴장을 실제로
|
방식** — 한때는 이 문서의 "mutate-in-place vs `fromState`" 긴장을 풀어보려
|
||||||
풀어보려 한 유일한 시도라 **quad-v2에서 제대로 다시 설계해볼 만한 후보**.
|
한 유일한 시도로서 다시 설계해볼 후보였으나, **아래 "종합"에서 최종적으로
|
||||||
단, 원본은 "내가 지금 유일한 tip인가" 체크에 소유권/버전 관리가 전혀 없어서
|
폐기됨** — `state(state)` 조합 모델이 소유권/버전 가드 없이도 같은 문제를
|
||||||
경쟁 상황에 취약했고 테스트/실사용 검증도 없었음 — **그대로 베끼지 말고,
|
더 간단히 풀어서 이 절충안 자체가 불필요해짐. 원본이 갖고 있던 진짜 결함
|
||||||
같은 아이디어를 소유권 가드를 제대로 넣어 재설계할 것.**
|
(소유권/버전 관리 없이 경쟁 상황에 취약, 테스트/실사용 검증도 없었음)은
|
||||||
|
기록으로만 남김.
|
||||||
- **`Depend(...)` 액션** — 계산값에는 관여하지 않고 오직 "이 소스가 바뀌면
|
- **`Depend(...)` 액션** — 계산값에는 관여하지 않고 오직 "이 소스가 바뀌면
|
||||||
다시 계산하라"는 추가 의존성만 등록하는 값-투명(value-transparent) no-op
|
다시 계산하라"는 추가 의존성만 등록하는 값-투명(value-transparent) no-op
|
||||||
액션. 작지만 깔끔한 아이디어라 이름 그대로 채택할 만함.
|
액션. 작지만 깔끔한 아이디어라 이름 그대로 채택할 만함.
|
||||||
|
|
@ -472,7 +494,7 @@ Service` 기반으로 구현)로 두면 됨 — 별도 `On` 모듈/필드 접근
|
||||||
인덱스라 지금 quad-v2 스코프 밖 — Instance가 아닌 데이터에 태깅이 필요해질
|
인덱스라 지금 quad-v2 스코프 밖 — Instance가 아닌 데이터에 태깅이 필요해질
|
||||||
미래 시나리오를 위한 참고 자료로만 기록.
|
미래 시나리오를 위한 참고 자료로만 기록.
|
||||||
- **Store/State 전파 모델, 라이프사이클 — 둘 다 재검토 후 기존 확정 유지**
|
- **Store/State 전파 모델, 라이프사이클 — 둘 다 재검토 후 기존 확정 유지**
|
||||||
(아래 "Store/State/Source 온톨로지" 절의 "PA님 코드와의 교차검증" 참고).
|
(위 "Store/State/Source 온톨로지" 절의 "PA님 코드와의 교차검증" 참고).
|
||||||
|
|
||||||
## 남은 열린 질문 (`.claude/question.md`에도 취합)
|
## 남은 열린 질문 (`.claude/question.md`에도 취합)
|
||||||
|
|
||||||
|
|
@ -493,9 +515,12 @@ Service` 기반으로 구현)로 두면 됨 — 별도 `On` 모듈/필드 접근
|
||||||
검증 대상).
|
검증 대상).
|
||||||
|
|
||||||
**해소된 것**: "Store가 Store를 담는 경우 이중 해제(double-dispose) 방지가
|
**해소된 것**: "Store가 Store를 담는 경우 이중 해제(double-dispose) 방지가
|
||||||
필요한가"는 재검토 결과 질문 자체가 성립 안 함으로 결론 — State/Source
|
필요한가"는 재검토 결과 질문 자체가 성립 안 함으로 결론 — 두 가지 독립적인
|
||||||
그래프 구독이 전부 weak-keyed GC-native(명시적 `dispose()` 호출이 아예 없음,
|
이유로 이중 해소됨. (1) 애초에 그런 경우를 만들지 않기로 확정(위 "Store가
|
||||||
`base/lifecycle-pattern.md`의 GC 위임 원칙 재사용)라 "같은 걸 두 번 해제"할
|
Store를 저장 가능한가" 절, 2026-08-04 6차 — Store는 Source에 준하는 "시작점"
|
||||||
행위 자체가 존재하지 않음(GC는 멱등). "`:Compute`가 with한 값을 어떻게
|
이라 다른 반응형 값을 담아 자동 연결되는 용도로 안 씀). (2) 설령 발생해도
|
||||||
|
State/Source 그래프 구독이 전부 weak-keyed GC-native(명시적 `dispose()` 호출이
|
||||||
|
아예 없음, `base/lifecycle-pattern.md`의 GC 위임 원칙 재사용)라 "같은 걸 두 번
|
||||||
|
해제"할 행위 자체가 존재하지 않음(GC는 멱등). "`:Compute`가 with한 값을 어떻게
|
||||||
읽는가"/"emit 필요 여부"도 전파 모델 확정으로 해소, `RobloxFactory` 중복
|
읽는가"/"emit 필요 여부"도 전파 모델 확정으로 해소, `RobloxFactory` 중복
|
||||||
호출/충돌 시나리오·인스턴스 생성/이벤트 네이밍도 위 절에서 전부 확정.
|
호출/충돌 시나리오·인스턴스 생성/이벤트 네이밍도 위 절에서 전부 확정.
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ Store/Slot/Tween/bind-dispatch 설계 결정에 인용되는 원본 비교 자
|
||||||
|
|
||||||
| 축 | Fusion | Vide | quad-v2 시사점 |
|
| 축 | Fusion | Vide | quad-v2 시사점 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 전파 모델 | push+pull 하이브리드, eager 집합만 즉시 재계산, 생성순 정렬로 글리치 방지 | 순수 push, 즉시 동기 재평가, 다이아몬드 중복 재평가 미해결(저자 인정) | "Store는 값 자체에 항상 eager 발화, cleanup이 key/value를 먼저 확인" 요구사항은 Vide의 push 모델 + eval-전-cleanup 패턴에 더 가까움. 단 Vide의 naive BFS 대신 Fusion의 생성순 정렬 글리치 방지 규율은 채택할 것. |
|
| 전파 모델 | push+pull 하이브리드, eager 집합만 즉시 재계산, 생성순 정렬로 글리치 방지 | 순수 push, 즉시 동기 재평가, 다이아몬드 중복 재평가 미해결(저자 인정) | ⚠️ **[정정] 아래 서술은 리서치 당시(2026-08-03 이전) 검토 방향이며 이후 뒤집힘 — 최종 확정은 `base/bind-system-plan.md`의 "전파 모델 확정" 절 참고**(push-invalidate는 신호만 쏘고 값은 안 실음, 재계산은 `Get()` 시점 pull-recompute로만, Fusion식 eager 노드·생성순 정렬은 아예 채택 안 함 — quad엔 그런 다단계 즉시 재계산이 필요한 소비자가 없다는 판단). 당시 스냅샷 원문: "Store는 값 자체에 항상 eager 발화, retract(구 cleanup)가 key/value를 먼저 확인" 요구사항은 Vide의 push 모델 + eval-전-retract 패턴에 더 가까움. 단 Vide의 naive BFS 대신 Fusion의 생성순 정렬 글리치 방지 규율은 채택할 것. |
|
||||||
| 정리/스코프 | 배열+메타테이블, dependency-agnostic, bind 시점에만 lifetime soft-check | dependency edge와 구조적 owner를 분리한 2중 관계, destroy는 owned만 cascade, 활성 스코프 destroy 하드 가드 | 둘 다 GC 비의존 eager 수동 정리 — rbvm의 Connected+GC 관용구와 정반대 축. quad의 Slot은 Vide처럼 "마운트 소유권"과 "반응 의존성"을 별개 관계로 분리하는 게 안전해 보임(`base/lifecycle-pattern.md`의 rbvm 패턴과는 다른 층위 — rbvm은 인스턴스 파괴 감지, 이건 Slot 내부 소유권 모델). |
|
| 정리/스코프 | 배열+메타테이블, dependency-agnostic, bind 시점에만 lifetime soft-check | dependency edge와 구조적 owner를 분리한 2중 관계, destroy는 owned만 cascade, 활성 스코프 destroy 하드 가드 | 둘 다 GC 비의존 eager 수동 정리 — rbvm의 Connected+GC 관용구와 정반대 축. quad의 Slot은 Vide처럼 "마운트 소유권"과 "반응 의존성"을 별개 관계로 분리하는 게 안전해 보임(`base/lifecycle-pattern.md`의 rbvm 패턴과는 다른 층위 — rbvm은 인스턴스 파괴 감지, 이건 Slot 내부 소유권 모델). |
|
||||||
| 키/값 디스패치 개방성 | SpecialKey 모양은 열려있으나 우선순위 4단계 하드고정 | action()은 등록 없는 태그 인식 방식이지만 key/value 버림, 콜백+우선순위만 | quad는 Fusion의 "디스패처가 key+value+target을 다 받는" 풍부함과 Vide의 "등록 없이 태그로 인식" 인체공학을 합치되, 우선순위 축은 열린 숫자 공간으로 일반화해야 함(`base/bind-system-plan.md`). |
|
| 키/값 디스패치 개방성 | SpecialKey 모양은 열려있으나 우선순위 4단계 하드고정 | action()은 등록 없는 태그 인식 방식이지만 key/value 버림, 콜백+우선순위만 | quad는 Fusion의 "디스패처가 key+value+target을 다 받는" 풍부함과 Vide의 "등록 없이 태그로 인식" 인체공학을 합치되, 우선순위 축은 열린 숫자 공간으로 일반화해야 함(`base/bind-system-plan.md`). |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# 라이프사이클 패턴 — rbvm의 `Connected` + GC 관용구 채택
|
# 라이프사이클 패턴 — rbvm의 `Connected` + GC 관용구 채택
|
||||||
|
|
||||||
**상태**: 결정됨(base) — quad-v2가 채택할 라이프사이클/정리(cleanup) 전략의 원본.
|
**상태**: 결정됨(base) — quad-v2가 채택할 라이프사이클/정리(`retract`) 전략의 원본.
|
||||||
완료 개념 없음, 구현하면서 세부 조정 있을 수 있음.
|
완료 개념 없음, 구현하면서 세부 조정 있을 수 있음.
|
||||||
|
|
||||||
## 배경
|
## 배경
|
||||||
|
|
@ -42,7 +42,7 @@ rbvm은 실제 Roblox Instance의 파괴를 감지하는 지점을 단 하나로
|
||||||
플래그를 그 콜백에서만 true로 뒤집음. `AncestryChanged`나 폴링 방식은 안 씀.
|
플래그를 그 콜백에서만 true로 뒤집음. `AncestryChanged`나 폴링 방식은 안 씀.
|
||||||
quad-v2도 동일: 인스턴스 라이프사이클 훅 지점은 `Destroying` 하나로 통일.
|
quad-v2도 동일: 인스턴스 라이프사이클 훅 지점은 `Destroying` 하나로 통일.
|
||||||
|
|
||||||
### 3. 정리(cleanup)는 기본적으로 GC에 위임, 예외적으로만 즉시(eager)
|
### 3. 정리(`retract`)는 기본적으로 GC에 위임, 예외적으로만 즉시(eager)
|
||||||
|
|
||||||
rbvm 전역에 약한 테이블(weak table, `__mode = "k"/"v"/"kv"`)로 private 데이터를
|
rbvm 전역에 약한 테이블(weak table, `__mode = "k"/"v"/"kv"`)로 private 데이터를
|
||||||
저장 — 홀더 객체를 아무도 안 들고 있으면 그 private 레코드도 자동으로 사라짐.
|
저장 — 홀더 객체를 아무도 안 들고 있으면 그 private 레코드도 자동으로 사라짐.
|
||||||
|
|
@ -51,13 +51,17 @@ rbvm 전역에 약한 테이블(weak table, `__mode = "k"/"v"/"kv"`)로 private
|
||||||
전체가 통째로 죽을 때의 순서 있는 dispose 훅. **quad-v2 원칙: 기본은 GC 위임,
|
전체가 통째로 죽을 때의 순서 있는 dispose 훅. **quad-v2 원칙: 기본은 GC 위임,
|
||||||
즉시 정리는 "안 끊으면 죽은 참조를 순회하게 되는 작은 포인터"류에만 국한.**
|
즉시 정리는 "안 끊으면 죽은 참조를 순회하게 되는 작은 포인터"류에만 국한.**
|
||||||
|
|
||||||
### 4. Signal 자체는 커스텀 구현체를 그대로 재사용 가능
|
### 4. (참고 기록) rbvm의 Signal 자체는 재사용 가능한 범용 emitter였음 — 실제로는 채택 안 함
|
||||||
|
|
||||||
`signal.luau`의 `Signal`/`Connection` 클래스는 rbvm 프록시 시스템에 의존하지
|
`signal.luau`의 `Signal`/`Connection` 클래스는 rbvm 프록시 시스템에 의존하지
|
||||||
않는 범용 이벤트 emitter임 (`Connect`/`Once`/`Wait`/`Fire`/`Destroy`,
|
않는 범용 이벤트 emitter임 (`Connect`/`Once`/`Wait`/`Fire`/`Destroy`,
|
||||||
`IsInited`/`OnInit`/`OnUninit` 지연 활성화 훅 포함). **단, 사용자 원 메모에는
|
`IsInited`/`OnInit`/`OnUninit` 지연 활성화 훅 포함). 사용자 원 메모에는
|
||||||
"시그널 자체 구현은 아닌듯... 콜백 정도로도 충분"이라고 되어 있어 서로 상충함**
|
"시그널 자체 구현은 아닌듯... 콜백 정도로도 충분"이라는 언급이 있어 한때
|
||||||
— 아래 열린 질문 참고.
|
이 문서 초안 단계에서 상충하는 것처럼 보였으나, **이 질문은 2026-08-04
|
||||||
|
검증 라운드에서 최종 확정으로 재확인됨 — 더 이상 열린 질문 아님**
|
||||||
|
(`base/architecture.md` 11번 항목도 동일하게 명시). 결론은 아래 "확정: Signal
|
||||||
|
클래스는 안 만든다" 절 참고 — 커스텀 `Signal`/`Connection` 클래스는 만들지
|
||||||
|
않고, 콜백 + `Connected` 계산 속성만 채택한다.
|
||||||
|
|
||||||
### 5. rbvm에서 그대로 가져오면 안 되는 것 (버그 발견됨)
|
### 5. rbvm에서 그대로 가져오면 안 되는 것 (버그 발견됨)
|
||||||
|
|
||||||
|
|
@ -97,8 +101,9 @@ Destroy되면 그 대상에 묶인 것들(Tween 등)도 자연히 죽은 상태
|
||||||
|
|
||||||
이 원칙 때문에 "값 교체 시 이전 처리를 무르는 것"(아래 `retract`)과 "완전
|
이 원칙 때문에 "값 교체 시 이전 처리를 무르는 것"(아래 `retract`)과 "완전
|
||||||
소멸 시 정리"는 **하나로 통일** — 후자는 애초에 안 만듦. `research/
|
소멸 시 정리"는 **하나로 통일** — 후자는 애초에 안 만듦. `research/
|
||||||
tween-plan.md`/`base/slot-plan.md`의 "cleanup" 표기는 전부 `retract`로
|
tween-plan.md`/`base/slot-plan.md`의 "cleanup" 표기는 대부분 `retract`로
|
||||||
갱신됨(이름 변경 근거는 아래).
|
갱신됨(이름 변경 근거는 아래) — 잔여 표기 확인은 진행 중, 해당 문서들은
|
||||||
|
각자 별도로 정리될 예정.
|
||||||
|
|
||||||
## 함수 안에서 만든 옵저버도 GC 대상이 되어야 함 — 범용 "생명 바인드 유틸" 필요
|
## 함수 안에서 만든 옵저버도 GC 대상이 되어야 함 — 범용 "생명 바인드 유틸" 필요
|
||||||
|
|
||||||
|
|
@ -180,4 +185,5 @@ Roblox 엔진 자체가 Destroy 시 Tag/Attribute/실행 중인 Tween을 전부
|
||||||
쉬움) — 실제 의미는 "이전에 적용한 처리를 무른다/멈춘다"이므로 **`retract`**
|
쉬움) — 실제 의미는 "이전에 적용한 처리를 무른다/멈춘다"이므로 **`retract`**
|
||||||
로 통일. (`revert`, `rescind`도 검토했으나 `retract`가 "이전에 취한 조치를
|
로 통일. (`revert`, `rescind`도 검토했으나 `retract`가 "이전에 취한 조치를
|
||||||
철회한다"는 의미로 가장 정확 — `process`/`retract` 쌍으로 자연스럽게 대구를
|
철회한다"는 의미로 가장 정확 — `process`/`retract` 쌍으로 자연스럽게 대구를
|
||||||
이룸.) 모든 문서에서 이 이름으로 갱신.
|
이룸.) 대부분의 문서에서 이 이름으로 갱신됨 — 잔여 "cleanup" 표기가 남은
|
||||||
|
문서가 있을 수 있으며, 그 확인/정리는 진행 중.
|
||||||
|
|
|
||||||
145
.claude/base/modifier-plan.md
Normal file
145
.claude/base/modifier-plan.md
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
# Modifier 설계 (정적 merge, immutable 체이닝)
|
||||||
|
|
||||||
|
**상태**: base — 핵심 메커니즘(런타임 plug 아님/정적 merge, immutable
|
||||||
|
값+clone 기반 체이닝, 이중 setter)은 2026-08-04 세션 채팅 논의로 확정. 남은
|
||||||
|
건 getter 정확한 이름뿐(구현 단계). Modifier가 컴포넌트 경계를 어떻게
|
||||||
|
통과하는지(다중 루트, 상속 방식)는 별개 문제로
|
||||||
|
`research/component-composition-plan.md`의 열린 질문에 남음 — 이 문서는
|
||||||
|
"Modifier 값 자체가 어떻게 동작하는가"만 다룸.
|
||||||
|
|
||||||
|
## 문제
|
||||||
|
|
||||||
|
`base/architecture.md` 7번 항목("Style(Default) 시스템 폐기, modifier
|
||||||
|
지향")이 방향만 정하고, 실제 메커니즘은 미정이었음: 핸들러 레지스트리에
|
||||||
|
넣을 것인가, 여러 modifier가 같은 키를 건드리면 어떻게 되는가, 트리를
|
||||||
|
타고 내려가며 조금씩 변형되는 modifier(예: 문서 뷰어의 TextStyle 상속)를
|
||||||
|
어떻게 안전하게 다룰 것인가.
|
||||||
|
|
||||||
|
## 확정된 결론
|
||||||
|
|
||||||
|
### 1. 런타임 pluggable 핸들러 아님 — 정적 merge
|
||||||
|
|
||||||
|
Modifier는 `isHandlable`/`priority`/`process`/`retract` 핸들러 레지스트리에
|
||||||
|
안 들어감. 그냥 평범한 테이블(데이터)을 보유하는 값이고, 디스패치 들어가기
|
||||||
|
전에 한 번 평탄화(flatten)돼서 최종 props 테이블에 합쳐짐. 이유: 런타임
|
||||||
|
pluggable로 만들면 여러 modifier가 반응형으로 같은 키를 계속 다투는 CSS
|
||||||
|
cascade 문제가 그대로 오는데, 이건 이미 확정된 "Store 바인드 변경은 전체
|
||||||
|
교체, 부분 오버레이 없음"(`base/architecture.md` 3번) 원칙과 충돌함.
|
||||||
|
|
||||||
|
### 2. Merge 우선순위: 배열 순서와 인라인은 독립된 두 규칙
|
||||||
|
|
||||||
|
`Frame { modifier1, modifier2, Name = ... }` 평탄화 시:
|
||||||
|
(a) 배열에 나열된 modifier들끼리는 순서상 나중 것이 우선.
|
||||||
|
(b) 명시적 키(인라인)는 modifier가 뭘 하든 무조건 우선.
|
||||||
|
Lua 테이블 리터럴은 배열 파트/해시 파트 사이에 소스 텍스트 순서를 보존하지
|
||||||
|
않으므로 "순서상 나중이 이긴다"는 단일 규칙만으로는 구현 불가 — 반드시 두
|
||||||
|
규칙으로 쪼개야 함.
|
||||||
|
|
||||||
|
### 3. Immutable 값 + clone 기반 체이닝
|
||||||
|
|
||||||
|
컴포지션 트리를 타고 내려가며 조금씩 변형되는 modifier(문서 뷰어에서 상위
|
||||||
|
TextStyle을 상속해 타이틀만 1.2배 키우는 경우 — Jetpack Compose의
|
||||||
|
`TextStyle.merge()`/`CompositionLocal`과 동일한 use case)는 특히 위험함 —
|
||||||
|
mutable하게 구현하면 같은 modifier 레퍼런스를 공유하는 형제 서브트리가
|
||||||
|
오염되거나(한쪽이 mutate하면 다른 쪽도 영향받음), 재렌더 시 값이 누적
|
||||||
|
드리프트하는 버그가 생김(`.claude/question.md` 초기 논의의 "원본 테이블
|
||||||
|
덮어쓰기/루프 깨짐" 우려와 동일 클래스).
|
||||||
|
|
||||||
|
**해결**: 모든 변환 메소드(`:FontSize(...)`류 체이닝)는 내부에서
|
||||||
|
`table.clone(self)`로 새 테이블을 만든 뒤 필드만 덮어써 반환 — 원본은
|
||||||
|
절대 mutate하지 않음. 별도의 제네릭 clone 콤비네이터 타입
|
||||||
|
(`modifier<<Frame>>(modifier):Set` 류 아이디어)은 기각 — 그런 타입을 만들면
|
||||||
|
`base/architecture.md` 3번의 "복사 구현 지양, 필요한 곳만 팩토리 함수로
|
||||||
|
명시적 복사" 원칙을 다시 재작업하는 셈이라, 각 변환 메소드 자체가 그 원칙을
|
||||||
|
따라 알아서 최소한만 복사하면 충분.
|
||||||
|
|
||||||
|
**성능**: Luau `table.clone`은 native shallow-copy라 modifier 크기(보통
|
||||||
|
한 자리~여남은 개 필드) 기준 비용 무시 가능, 렌더/컴포지션 타임에만
|
||||||
|
발생(프레임마다 도는 게 아님). State가 이미 `:With`/`:Compute`마다 새
|
||||||
|
노드를 할당하는 것과 같은 급의 비용이라 일관되고, mutable+문서화 경고보다
|
||||||
|
오염 버그를 원천 차단하는 쪽이 라이브러리 복잡도/사용자 편의 양쪽에서
|
||||||
|
낫다고 판단 — **immutable 기본으로 확정**.
|
||||||
|
|
||||||
|
### 4. Setter는 리터럴 값과 변환 함수 둘 다 받음
|
||||||
|
|
||||||
|
`:FontSize(value)`(리터럴) / `:FontSize(function(current) return
|
||||||
|
current*1.2 end)`(변환 함수) 둘 다 지원 — 한 줄로 끝내고 싶을 때는 콜백,
|
||||||
|
여러 줄로 풀어쓰고 싶을 때는 현재 값을 getter로 꺼내 계산 후 리터럴로
|
||||||
|
다시 넣는 스타일 둘 다 인체공학상 필요하다고 판단.
|
||||||
|
|
||||||
|
변환 함수는 State의 `:Compute`처럼 lazy State 핸들을 넘길 필요가 없음(*필드가
|
||||||
|
순수 데이터인 일반적인 경우에 한해* — 필드가 State일 때의 예외는 아래 참고).
|
||||||
|
계산 비용 자체가 없는 순수 데이터라면 콜백엔 그냥 raw 현재 값을 즉시 넘기면
|
||||||
|
충분(State의 self-lazy-핸들 문제와는 다른 카테고리).
|
||||||
|
|
||||||
|
**내부 구현**: `__real` 같은 별도 래퍼는 불필요해 보임 — 데이터를 테이블에
|
||||||
|
직접 두고 메소드는 공유 메타테이블 `__index`로 붙이면, `table.clone`이
|
||||||
|
메타테이블까지 그대로 복사해주는 Luau 동작 덕분에 클론해도 체이닝이 안
|
||||||
|
끊김. flatten도 그 테이블 필드를 직접 읽으면 됨.
|
||||||
|
|
||||||
|
**Getter 정확한 모양은 미정** — `mod:Get("FontSize")` 같은 전용 메소드로
|
||||||
|
할지, 아니면 Store/DI 관습처럼 dot-access(`mod.fontSize`) 자체가 읽기
|
||||||
|
경로를 겸하게 해서 별도 `:Get()`이 아예 불필요하게 할지는 구현 단계에서
|
||||||
|
확정. **다만 getter의 동작 자체은 확정**: 필드가 State면 getter 호출이
|
||||||
|
곧 관측이라 그 순간 계산되어 확정된(더 이상 반응하지 않는) 값이 반환됨 —
|
||||||
|
`base/bind-system-plan.md`의 "관측해야 실체화된다" 전역 원칙 그대로 적용
|
||||||
|
(아래 참고).
|
||||||
|
|
||||||
|
### 4-1. 필드가 State일 수도 있음 — Setter가 State/plain 여부로 분기
|
||||||
|
|
||||||
|
`architecture.md` 7번 항목이 "함수형 modifier가 store 바인드를 받을 수도
|
||||||
|
있음"이라고 이미 언급한 대로, Modifier 필드는 plain 값뿐 아니라 State일
|
||||||
|
수도 있음(예: 상위에서 내려온 테마 색상이 Store에 바인드된 반응형 값).
|
||||||
|
이 경우 위 4번의 setter가 그대로 통하려면, **현재 저장된 필드 값이 State냐
|
||||||
|
plain이냐에 따라 setter 내부 동작이 갈려야 함** — 새 개념이 아니라 State에
|
||||||
|
이미 있는 lazy/`:Compute` 체이닝을 그대로 재사용하는 것뿐:
|
||||||
|
|
||||||
|
| 현재 필드 | 인자 | 동작 |
|
||||||
|
|---|---|---|
|
||||||
|
| plain | 리터럴 | clone 후 그 값으로 덮어씀 |
|
||||||
|
| plain | 함수 | clone 후 즉시 호출해 나온 값으로 덮어씀(현재 값이 raw로 넘어감) |
|
||||||
|
| **State** | **리터럴** | clone 후 **State를 통째로 리터럴로 덮어씀 — 의도적으로 반응성이 끊김**(Store의 "부분 오버레이 없음, 전체 교체" 원칙과 같은 결) |
|
||||||
|
| **State** | **함수** | clone 후 `field:Compute(fn)`으로 **새 파생 State**를 만들어 대입 — 반응성 유지, State의 기존 `:Compute` 메커니즘에 그대로 위임 |
|
||||||
|
|
||||||
|
즉 함수형 셋터는 필드가 State일 때 반응성을 보존하고, 리터럴 셋터(혹은
|
||||||
|
getter로 꺼내 계산 후 리터럴로 다시 넣는 멀티라인 스타일)는 그 순간 값을
|
||||||
|
확정시켜 반응성을 끊음 — 이 차이는 사용자가 인지하고 골라 쓰는 것으로
|
||||||
|
문서화.
|
||||||
|
|
||||||
|
### 4-2. Modifier는 소유권/유일성 제약이 없음
|
||||||
|
|
||||||
|
Modifier는 자식(child)을 담지 않음 — 마운트 정체성이 없는 순수 값. 그래서
|
||||||
|
어떤 컴포넌트가 특정 modifier를 실제로 적용하든 안 하든, 또 같은 modifier를
|
||||||
|
트리 여러 곳에 반복 적용하든 에러가 나지 않고 상관없음(Ref나 Slot 자식처럼
|
||||||
|
"정확히 한 곳에만 마운트돼야 한다"는 소유권 제약이 이들에게는 있지만
|
||||||
|
Modifier에는 없음).
|
||||||
|
|
||||||
|
### 5. 타입 출처는 이미 확정된 dot-access 관습 재사용
|
||||||
|
|
||||||
|
"누가 modifier에 타입을 붙여주냐"는 새 문제가 아니라, Store/인스턴스 생성에
|
||||||
|
이미 적용한 "정적으로 알려진 건 dot-access, 동적인 건 문자열 폴백" 프로젝트
|
||||||
|
전역 관습(`base/bind-system-plan.md` "타입 추론 문제" 절)을 그대로 적용하면
|
||||||
|
됨 — `Modifier.Rounded(8)`/`Modifier.FontSize(...)`처럼 DI 쪽 "제네릭
|
||||||
|
생성자 함수 하나 + 자주 쓰는 것만 정적 필드로 미리 바인딩" 패턴 재사용.
|
||||||
|
(주의: 이벤트는 이 관습의 유일한 예외라 인용 대상에서 제외 — 이벤트 바인딩은
|
||||||
|
PA님 방식인 문자열 키 + 런타임 리플렉션으로 감, `base/bind-system-plan.md`
|
||||||
|
"이벤트 바인딩 정정" 절 참고. Modifier는 이벤트가 아니라 Store/인스턴스
|
||||||
|
생성과 같은 카테고리라 dot-access 관습이 그대로 적용됨.)
|
||||||
|
|
||||||
|
### 6. State/Pipe 쪽엔 영향 없음 — 이미 있던 결정의 재확인일 뿐
|
||||||
|
|
||||||
|
Modifier가 immutable해야 하는 이유(변환마다 clone)와 State가 이미
|
||||||
|
"`:With`/`:Compute`마다 새 노드를 만든다"(`base/bind-system-plan.md` 2차
|
||||||
|
라운드 확정)로 확정해둔 이유는 같은 클래스의 문제(공유 mutable 상태로 인한
|
||||||
|
오염 방지)임을 이번 논의에서 재확인했을 뿐 — State/Source 온톨로지 자체엔
|
||||||
|
변경 사항 없음. 파이프 분기(`:With(...):Compute(fn)`)는 이미 코드에
|
||||||
|
명시적으로 쓰는 구조라 "암묵적 분기"가 애초에 존재하지 않음 — 새로 결정할
|
||||||
|
것 없음.
|
||||||
|
|
||||||
|
## 열린 질문 (`.claude/question.md`에도 취합)
|
||||||
|
|
||||||
|
- Getter 정확한 이름/모양(`:Get(key)` vs dot-access 겸용) — 후순위, 구현
|
||||||
|
단계에서 다른 세부 API 이름들과 같이 확정 가능.
|
||||||
|
- Modifier가 컴포넌트 경계를 어떻게 통과하는지(다중 루트, 상속 방식)는
|
||||||
|
`research/component-composition-plan.md`에서 계속 다룸 — 이 문서가 다루는
|
||||||
|
"값 자체의 동작"과는 별개 문제.
|
||||||
|
|
@ -53,9 +53,15 @@ init하려 하면 오류, 없는데 뭔가 생성해서 bind하려 해도 오류
|
||||||
- **Store 자체의 연산은 더 단순해져도 됨** — v1의 `:Add`/`:With`/`:Tween` 같은
|
- **Store 자체의 연산은 더 단순해져도 됨** — v1의 `:Add`/`:With`/`:Tween` 같은
|
||||||
이름 붙은 체이닝 연산(named modifier)은 명시적으로 안 만들기로 확정, 대신
|
이름 붙은 체이닝 연산(named modifier)은 명시적으로 안 만들기로 확정, 대신
|
||||||
일반 함수를 받는 형태로 통일(`base/store-semantics.md` 참고). "너무 verbose한
|
일반 함수를 받는 형태로 통일(`base/store-semantics.md` 참고). "너무 verbose한
|
||||||
연산들은 오히려 일관성을 해친다"는 게 이유.
|
연산들은 오히려 일관성을 해친다"는 게 이유. (주의: 아래의 v2 `:With(...)`는
|
||||||
|
이름만 같을 뿐 여기서 안 만들기로 한 v1의 `:With`와는 다른 연산임 — v1은
|
||||||
|
"함수/테이블에서 값을 가져오는" 가공 연산이었고, v2는 그냥 "여러 State를
|
||||||
|
의존성으로 모으는" 수집 연산.)
|
||||||
- **여러 store 값을 묶어 유연하게 처리하는 방법**(`useEffect`류 dependency
|
- **여러 store 값을 묶어 유연하게 처리하는 방법**(`useEffect`류 dependency
|
||||||
array)은 있으면 좋겠다는 요청 — API 시그니처는 미정, `base/bind-system-plan.md`의 남은 열린 질문 참고.
|
array)은 있으면 좋겠다는 요청이었고 — **API 시그니처도 확정됨**:
|
||||||
|
`:With(...)`로 의존성을 모으고 `:Compute(fn)`으로 파생 State를 만드는
|
||||||
|
형태, 상세는 `base/store-semantics.md`의 "여러 스토어 값을 묶어 처리하는
|
||||||
|
것" 절 참고.
|
||||||
- `can execute store bind` 후킹 자체는 `Connected` 계산 속성으로 대체된다는
|
- `can execute store bind` 후킹 자체는 `Connected` 계산 속성으로 대체된다는
|
||||||
잠정 제안이 그대로 유지되고, 여기에 더해 **완전 소멸(Destroy) 시점엔 아무
|
잠정 제안이 그대로 유지되고, 여기에 더해 **완전 소멸(Destroy) 시점엔 아무
|
||||||
처리도 필요 없다**는 원칙까지 확정됨(`base/lifecycle-pattern.md`) — 즉 이
|
처리도 필요 없다**는 원칙까지 확정됨(`base/lifecycle-pattern.md`) — 즉 이
|
||||||
|
|
@ -93,6 +99,6 @@ init하려 하면 오류, 없는데 뭔가 생성해서 bind하려 해도 오류
|
||||||
유틸은 인터페이스, 실제 구현은 백엔드 팩토리가 주입" 절 참고. **중복 호출
|
유틸은 인터페이스, 실제 구현은 백엔드 팩토리가 주입" 절 참고. **중복 호출
|
||||||
가드/`New()`와의 관계는 2026-08-04 3차 라운드에서 확정**: 같은 팩토리로
|
가드/`New()`와의 관계는 2026-08-04 3차 라운드에서 확정**: 같은 팩토리로
|
||||||
재호출하면 무시(no-op), 다른 팩토리로 재호출하면 에러(유일 슬롯 충돌 —
|
재호출하면 무시(no-op), 다른 팩토리로 재호출하면 에러(유일 슬롯 충돌 —
|
||||||
바로 아래 "Bind는 누가, 어떻게 구현하는가" 절의 원칙과 일치) — `New()`가
|
바로 위 "Bind는 누가, 어떻게 구현하는가" 절의 원칙과 일치) — `New()`가
|
||||||
생기면 인스턴스별 테이블이 분리되므로 이 가드도 자연히 인스턴스별로
|
생기면 인스턴스별 테이블이 분리되므로 이 가드도 자연히 인스턴스별로
|
||||||
스코핑됨, 별도 재설계 불필요.
|
스코핑됨, 별도 재설계 불필요.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
# 컴포넌트 순수성이 아니라 "이식성" 문제 (재정의됨)
|
# 컴포넌트 순수성이 아니라 "이식성" 문제 (재정의됨)
|
||||||
|
|
||||||
**상태**: research — 사용자 확인 완료로 문제 자체는 명확해짐, 남은 건 문서화
|
**상태**: base — 확정됨(2026-08-04 세션에 `research/`에서 승격). 남은 건
|
||||||
강도 정도. 원본: `.claude/initreq/raw-userinput.md` "순수함수에 대한 범위를
|
가이드 문서 내 배치 위치 정도로 기술적 결정 사항은 없음. 원본:
|
||||||
정할 필요가 있음" / "진짜 부작용은 외부에 만들어버린다" 절.
|
`.claude/initreq/raw-userinput.md` "순수함수에 대한 범위를 정할 필요가 있음" /
|
||||||
|
"진짜 부작용은 외부에 만들어버린다" 절.
|
||||||
|
|
||||||
## 정정: "순수함수 여부"가 아니라 "이식성(portability)" 문제였다
|
## 정정: "순수함수 여부"가 아니라 "이식성(portability)" 문제였다
|
||||||
|
|
||||||
|
|
@ -24,7 +24,8 @@ Mount(ScreenGui, Frame {...})
|
||||||
|
|
||||||
`Class.Extend()`로 재사용 컴포넌트(`Init/Render/AfterRender/Getter/Setter/
|
`Class.Extend()`로 재사용 컴포넌트(`Init/Render/AfterRender/Getter/Setter/
|
||||||
UpdateTriggers/Unload`) 정의 가능. `Store.GetObject(id)`류 id 기반 전역 조회는
|
UpdateTriggers/Unload`) 정의 가능. `Store.GetObject(id)`류 id 기반 전역 조회는
|
||||||
v2에서 태그 시스템으로 대체 예정(`base/store-and-tags.md` 참고).
|
v2에서 대체될 예정 — Ref 도입과 네임스페이싱 판단까지 포함해 최신 상세는
|
||||||
|
`base/architecture.md` 5번 항목 참고.
|
||||||
|
|
||||||
## 핵심 내부 동작 요약
|
## 핵심 내부 동작 요약
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,14 +65,24 @@ Slot은 하나의 instance 안에 여럿 존재할 수 있다. 전부 하나의
|
||||||
편하다는 방향. **기울어진 결론**: 별도 "Named Slot" 개념 없이, store나
|
편하다는 방향. **기울어진 결론**: 별도 "Named Slot" 개념 없이, store나
|
||||||
파라미터로 넘기고 그게 그냥 ref처럼 바인드되는 모양.
|
파라미터로 넘기고 그게 그냥 ref처럼 바인드되는 모양.
|
||||||
|
|
||||||
|
**상태 확인(2026-08-04 문서 정리 시점)**: 이 방향은 아래 "열린 질문" 절이나
|
||||||
|
`.claude/question.md`의 확정 목록 어디에도 명시적으로 흡수된 흔적이 없음 —
|
||||||
|
아직 정식 확정 절차(`AskUserQuestion` 등)를 거치지 않은 것으로 보임. **열린
|
||||||
|
질문으로 유지**, `.claude/question.md`에도 반영 필요.
|
||||||
|
|
||||||
## Slot과 Store 바인드의 관계 (`retract` 순서)
|
## Slot과 Store 바인드의 관계 (`retract` 순서)
|
||||||
|
|
||||||
Slot이 store 바인드로 들어오는 경우, pluggable 처리기에 `retract`(구 cleanup,
|
Slot이 store 바인드로 들어오는 경우, pluggable 처리기에 `retract`(구 cleanup,
|
||||||
`base/lifecycle-pattern.md` 참고) 핸들러가 필요함 — 한번 넘어간 slot 요소가
|
`base/lifecycle-pattern.md` 참고) 핸들러가 필요함 — 한번 넘어간 slot 요소가
|
||||||
나중에 `retract`되면 삭제되는지, 아니면 "부모의 소유이니 부모가 처리"해야
|
나중에 `retract`되면 삭제되는지, 아니면 "부모의 소유이니 부모가 처리"해야
|
||||||
하는지 검토 필요. **기울어진 결론**: 부모가 정리 정도만 미리 수행하고 다시
|
하는지 검토 필요. **기울어진 결론(잠정안, 이후 정정됨)**: 부모가 정리 정도만
|
||||||
`process`하면 되므로, 부모에게 위임(자식 slot 요소 자체가 스스로 정리를
|
미리 수행하고 다시 `process`하면 되므로, 부모에게 위임(자식 slot 요소 자체가
|
||||||
실행하는 게 아니라).
|
스스로 정리를 실행하는 게 아니라).
|
||||||
|
|
||||||
|
> **정정(2026-08-04 검증 라운드)**: 위 "부모 위임" 잠정안은 이후 **폐기**
|
||||||
|
> 쪽으로 정정됨 — 아래 "확정" 절과 `.claude/question.md`("Slot의 `retract`
|
||||||
|
> 동작이 '부모 위임' 잠정안에서 '폐기(옮기지 않음)'로 확정") 참고. 이 문단은
|
||||||
|
> 검토 과정의 히스토리로만 남겨둠, 현재 유효한 동작 아님.
|
||||||
|
|
||||||
이건 `base/bind-system-plan.md`의 "Store 바인드는 재실행 래핑" 확정
|
이건 `base/bind-system-plan.md`의 "Store 바인드는 재실행 래핑" 확정
|
||||||
모델과 맞물림 — slot이 store 값으로 오면, store 바인드 핸들러가 이전 slot
|
모델과 맞물림 — slot이 store 값으로 오면, store 바인드 핸들러가 이전 slot
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# Store 의미론 — 부작용 허용, State는 Store 위의 조합 가능한 캐시 레이어
|
# Store 의미론 — 부작용 허용, State는 Store 위의 조합 가능한 캐시 레이어
|
||||||
|
|
||||||
**상태**: base — 부작용 허용/Store 문법 부분은 확정. State/Source 온톨로지는
|
**상태**: base — 전부 확정. State/Source 온톨로지는 2026-08-04 검증
|
||||||
2026-08-04 검증 라운드에서 새로 열린 진행 중인 설계 스레드(`base/bind-system-plan.md` 참고). 원본: `.claude/initreq/raw-userinput.md`
|
라운드에서 새로 열려 같은 세션 2~4차 라운드에 걸쳐 확정까지 마침 — 최신
|
||||||
|
상세는 `base/bind-system-plan.md` 참고. 원본: `.claude/initreq/raw-userinput.md`
|
||||||
"store는 부작용을 허용함" / "state는 어떻게 구현하는가" 절.
|
"store는 부작용을 허용함" / "state는 어떻게 구현하는가" 절.
|
||||||
|
|
||||||
## Store는 부작용을 허용하는 게 기본 디자인
|
## Store는 부작용을 허용하는 게 기본 디자인
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
|
|
||||||
다만 한 가지는 명확히 구분: **렌더 리턴 위에서 무언가를 observe하는 것은 그냥
|
다만 한 가지는 명확히 구분: **렌더 리턴 위에서 무언가를 observe하는 것은 그냥
|
||||||
부작용**이다 (`useEffect`와 유사한 것으로 문서화). 이건 "허용되는 부작용"이 아니라
|
부작용**이다 (`useEffect`와 유사한 것으로 문서화). 이건 "허용되는 부작용"이 아니라
|
||||||
"당연히 부작용"이라는 뜻 — 문서화 시 이 경계를 분명히 할 것 (`research/
|
"당연히 부작용"이라는 뜻 — 문서화 시 이 경계를 분명히 할 것 (`base/
|
||||||
purity-and-effects-plan.md`와 연결됨).
|
purity-and-effects-plan.md`와 연결됨).
|
||||||
|
|
||||||
**보강(2026-08-04 검증 라운드): 부작용은 심각도가 다른 두 갈래로 나뉜다.**
|
**보강(2026-08-04 검증 라운드): 부작용은 심각도가 다른 두 갈래로 나뉜다.**
|
||||||
|
|
@ -23,7 +24,7 @@ purity-and-effects-plan.md`와 연결됨).
|
||||||
2. **경계를 넘는 부작용** — globalStore처럼 컴포넌트 바깥의 전역 상태를
|
2. **경계를 넘는 부작용** — globalStore처럼 컴포넌트 바깥의 전역 상태를
|
||||||
다루는 경우. 게임 UI 특성상(스킬/주변 환경에 영향받는 UI 등) 완전히
|
다루는 경우. 게임 UI 특성상(스킬/주변 환경에 영향받는 UI 등) 완전히
|
||||||
막을 수는 없지만, 라이브러리로 재사용하려는 컴포넌트가 이런 부작용을
|
막을 수는 없지만, 라이브러리로 재사용하려는 컴포넌트가 이런 부작용을
|
||||||
가지면 이식성이 떨어짐(`research/purity-and-effects-plan.md`와 연결).
|
가지면 이식성이 떨어짐(`base/purity-and-effects-plan.md`와 연결).
|
||||||
|
|
||||||
**해소됨(2026-08-04 2차 라운드)**: state를 옵저빙해서 나온 결과로 slot에
|
**해소됨(2026-08-04 2차 라운드)**: state를 옵저빙해서 나온 결과로 slot에
|
||||||
`clear`/`add` 같은 연산을 할 때, 그 시점에 대상 slot이 이미 죽어있으면
|
`clear`/`add` 같은 연산을 할 때, 그 시점에 대상 slot이 이미 죽어있으면
|
||||||
|
|
@ -60,14 +61,14 @@ pull-recompute)·`:Compute` 인자 규칙·State 쓰기 금지·`Source` 독립
|
||||||
방향으로 좁혀짐 — 별도 `Pipe` 타입을 만들어 소유권/버전 가드를 넣는 대신
|
방향으로 좁혀짐 — 별도 `Pipe` 타입을 만들어 소유권/버전 가드를 넣는 대신
|
||||||
State 자체가 "파이핑 결합체"이고 `state(state)`로 분기하면 될 걸로 보임
|
State 자체가 "파이핑 결합체"이고 `state(state)`로 분기하면 될 걸로 보임
|
||||||
(`Pipe` 후보는 사실상 폐기 쪽으로 기움). 상세는 `base/bind-system-plan.md`의
|
(`Pipe` 후보는 사실상 폐기 쪽으로 기움). 상세는 `base/bind-system-plan.md`의
|
||||||
"Store/State/Source 온톨로지" 절 참고 — **아직 완전히 결론난 설계는 아니고,
|
"Store/State/Source 온톨로지" 절 참고 — **이 절 이후 2~4차 라운드에 걸쳐
|
||||||
구현 단계에서 더 다뤄야 할 진행 중인 스레드.**
|
전부 확정됨, 더 이상 진행 중인 스레드 아님.**
|
||||||
|
|
||||||
미해결로 남은 것: `:Compute`의 캐싱/무효화 전략(값이 바뀌었는데 듣는 소비자가
|
과거 "미해결로 남은 것"으로 적었던 두 항목도 모두 해소됨: `:Compute`의
|
||||||
없으면 연산을 미루는 dirty-flag 방식 등), Luau 타입 시스템에서 `store "key"`
|
캐싱/무효화 전략은 push-invalidate(신호만)/pull-recompute(`Get()` 시점)로
|
||||||
같은 커링 호출이 오버로드 함수 타입으로 `state<T>`를 정확히 추론하기 어려운
|
확정(`base/bind-system-plan.md` "전파 모델" 절), `store "key"` 커링의 타입
|
||||||
문제(문자열 리터럴이 as-const로 좁혀지지 않는 문제) — 둘 다 열린 채로
|
추론 문제는 `store.key`(dot-access)를 1급 경로로 확정하며 해소(같은 문서
|
||||||
`base/bind-system-plan.md`에서 계속 다룰 것.
|
"타입 추론 문제" 절, 3차 라운드).
|
||||||
|
|
||||||
## Store 값 설정 문법 — v1 인체공학 유지 (확정)
|
## Store 값 설정 문법 — v1 인체공학 유지 (확정)
|
||||||
|
|
||||||
|
|
@ -92,8 +93,11 @@ pull-recompute)·`:Compute` 인자 규칙·State 쓰기 금지·`Source` 독립
|
||||||
`useEffect`처럼 여러 store 값을 디펜던시로 묶어 파생값을 계산하고 싶다는
|
`useEffect`처럼 여러 store 값을 디펜던시로 묶어 파생값을 계산하고 싶다는
|
||||||
요구가 있었음(v1의 `myStore "a,b"` 콤마-조인 문자열 방식은 폐기 대상 —
|
요구가 있었음(v1의 `myStore "a,b"` 콤마-조인 문자열 방식은 폐기 대상 —
|
||||||
`base/quad-v1-architecture.md`의 "문자열 DSL" 문제점 참고). **v1의
|
`base/quad-v1-architecture.md`의 "문자열 DSL" 문제점 참고). **v1의
|
||||||
`:Add`/`:With`/`:Tween` 같은 이름 붙은(named) 체이닝 연산은 만들지 않음** —
|
`:Add`/`:With`/`:Tween`처럼 값을 직접 가공하는 이름 붙은(named) 체이닝 연산은
|
||||||
대신 일반 함수를 받아 처리. 최종 형태는 `:With(...)`로 의존성을 모으고
|
만들지 않음** — 대신 일반 함수를 받아 처리. (주의: 아래의 v2 `:With(...)`는
|
||||||
|
이름만 같을 뿐 v1의 `:With`와는 다른 연산임 — v1은 "함수/테이블에서 값을
|
||||||
|
가져오는" 가공 연산이었고, v2는 그냥 "여러 State를 의존성으로 모으는" 수집
|
||||||
|
연산.) 최종 형태는 `:With(...)`로 의존성을 모으고
|
||||||
`:Compute(fn)`으로 파생 State를 만드는 것으로 확정 — `Store.Combine({a,b},
|
`:Compute(fn)`으로 파생 State를 만드는 것으로 확정 — `Store.Combine({a,b},
|
||||||
fn)`류 포지셔널 인자 방식은 기각됨. 정확한 lazy 인자 규칙(self/with 값 둘 다
|
fn)`류 포지셔널 인자 방식은 기각됨. 정확한 lazy 인자 규칙(self/with 값 둘 다
|
||||||
State 핸들로 넘기고 `.value`를 실제로 읽을 때만 계산)은 `base/bind-system-plan.md`의 "Store/State/Source 온톨로지" 절 참고.
|
State 핸들로 넘기고 `.value`를 실제로 읽을 때만 계산)은 `base/bind-system-plan.md`의 "Store/State/Source 온톨로지" 절 참고.
|
||||||
|
|
|
||||||
|
|
@ -1,194 +1,89 @@
|
||||||
# 확인/결정 필요 목록 (전체 취합)
|
# 확인/결정 필요 목록
|
||||||
|
|
||||||
각 plan 문서에 흩어진 "사용자 확인 필요" 절의 취합본. **막고 있는 항목은
|
**2026-08-04 세션 말미에 전체 재정리함.** 예전엔 라운드(1차~6차)별로 문서가
|
||||||
거의 없음** — 대부분 합리적 기본값/방향을 잡아두고 research 단계에 머물러
|
계속 쌓이면서 순서가 시간순도 우선순위순도 아니게 됐고, 이미 해소된 라운드
|
||||||
있음. 사용자가 Lua/Roblox 엔진에 대해 깊이 아는 사람이라는 전제로, 우선순위
|
기록이 새로 열린 질문보다 위에 있는 등 혼동을 유발했음(문서 감사에서 발견).
|
||||||
높은 것부터 정렬.
|
그 상세 히스토리는 지우지 않았음 — git log로 이 파일의 이전 버전을 보거나,
|
||||||
|
각 `base/`/`research/` 문서 안의 라운드 표시("2026-08-04 3차 라운드" 등)를
|
||||||
|
따라가면 그대로 남아있음. 이 문서는 이제 **"지금 열려있는 것" 우선으로만**
|
||||||
|
구성.
|
||||||
|
|
||||||
## 2026-08-04 5차 라운드 완료 — 소스 트리 구조 확정
|
## 지금 열려있는 것 (우선순위순)
|
||||||
|
|
||||||
`.claude/base/architecture.md`의 "구현 착수: 소스 트리 구조 확정" 절 참고.
|
### 1. [최우선] 컴포넌트 경계에서 modifier/Ref가 어떻게 전달되는가
|
||||||
`base/bind-system-plan.md`/`base/module-lifecycle-plan.md`/`base/slot-plan.md`가
|
|
||||||
이 라운드에서 `research/`에서 승격됨.
|
|
||||||
|
|
||||||
- **패키징**: 최종 목표는 다중 wally 패키지지만, 지금 Luau 툴링(wally 타입
|
사용자가 "지금 quad에서 가장 문제되는 부분"으로 직접 지목. 컴포넌트가
|
||||||
단절, `luau-lsp` 심볼릭 링크 해석 문제)이 불안정해서 당장은 모놀리식 —
|
플레인 함수이고 반환하는 루트가 여러 개(혹은 Slot으로 갈라지는 구조)일 때,
|
||||||
`Sleitnick/RbxUtil` 패턴(루트 통합 개발/테스트, 서브폴더마다 자체
|
호출부가 넘긴 modifier/Ref가 "어느 루트로 가야 하는지" 모호해지는 케이스가
|
||||||
`wally.toml`) 채택. `.luaurc` alias는 런타임 require에서 아직 엔진 미지원 —
|
있음. Jetpack Compose는 언어 강제가 아니라 "컴포저블은 `modifier` 파라미터를
|
||||||
편집기 경험용으로만 사용, 런타임 require는 상대경로.
|
받아 루트에 적용해야 한다"는 순수 관례(+린트)로 풂 — quad도 비슷한 관례
|
||||||
- **패키지 경계**: `quad-base` = Store/State/Source 온톨로지+전파 **+**
|
기반으로 갈 수 있어 보이나 다중 루트 케이스는 미정.
|
||||||
pluggable 디스패치 엔진(`process`/`retract`, 핸들러 계약, `LifetimeHandle`/
|
|
||||||
`PerInstanceState` 인터페이스, Ref, Slot 코어 재조정 로직) — 전부
|
|
||||||
"인터페이스"로, 다른 엔진(GTK 등)에서도 재사용 가능해야 한다는 전제.
|
|
||||||
`quad-roblox` = 위 인터페이스의 실제 구현체(`RobloxFactory`, Property/Event/
|
|
||||||
Attribute/Tag/Tween/Slot 적용 핸들러, `DI` 인스턴스 생성자) — 이유: 엔진마다
|
|
||||||
큰 구현을 중복하지 않기 위함(rbvm의 relation 통합 시도와 같은 동기).
|
|
||||||
- **Slot 패키지 경계**: 재조정 로직(add/remove/clear)은 base, 실제 Instance
|
|
||||||
`Parent`/`Destroy` 조작은 roblox의 핸들러가 담당 — `base/slot-plan.md`
|
|
||||||
"base/roblox 패키지 경계" 절.
|
|
||||||
- **새 핸들러 필요성 확인**: `k:number, v:Instance`(중첩 인스턴스를 직접
|
|
||||||
자식으로 넣는 경우, `Frame { Frame {} }`)를 위한 `InstanceChild` 핸들러가
|
|
||||||
Slot과 별개로 필요 — `quad-roblox/src/Handlers/InstanceChild.luau`.
|
|
||||||
|
|
||||||
## 2026-08-04 검증 라운드 완료
|
**주의**: modifier "값 자체"가 어떻게 동작하는지(정적 merge, immutable+clone
|
||||||
|
체이닝, State 필드 지원)는 이미 완전히 확정됨(`base/modifier-plan.md`) — 이
|
||||||
|
질문은 그것과 별개로 "경계를 어떻게 통과하느냐"만 다룸, 혼동하지 말 것.
|
||||||
|
|
||||||
아래 "확정됨" 절 전체(architecture.md 14개 항목, lifecycle-pattern.md,
|
→ 상세/배경: `research/component-composition-plan.md`.
|
||||||
store-semantics.md, bind-system-plan.md, module-lifecycle-plan.md,
|
|
||||||
slot-plan.md, tween-plan.md)를 `AskUserQuestion`으로 하나씩 예/아니오 재검증
|
|
||||||
완료 — 대부분 그대로 확인됐지만, 아래는 검증 과정에서 실제로 문서가 수정된
|
|
||||||
항목:
|
|
||||||
|
|
||||||
- **`State` 프리미티브는 "안 만든다"가 아니라 실제로 필요함** — 정정 완료,
|
### 2. 용어 정리 (사용자 요청, 진행 중)
|
||||||
`base/store-semantics.md` 참고. **이 결과로 Store/State/Source 온톨로지
|
|
||||||
전체가 새로운 열린 설계 스레드로 떠올랐음** — 아래 "최우선 새 열린 질문"
|
|
||||||
참고.
|
|
||||||
- Slot의 `retract` 동작이 "부모 위임" 잠정안에서 "폐기(옮기지 않음)"로 확정
|
|
||||||
— `base/slot-plan.md`.
|
|
||||||
- quad2-try의 `Pipe` copy-on-write 후보는 사실상 폐기, `state(state)` 조합
|
|
||||||
모델로 대체 — `base/bind-system-plan.md`.
|
|
||||||
- `Connected` 체크/GC 위임/`Destroying` 훅 관련 뉘앙스 보강(엔진별 인터페이스
|
|
||||||
주입, quad는 rbvm보다 즉시정리 필요성이 낮음) — `base/lifecycle-pattern.md`.
|
|
||||||
- base 유틸(per-instance 저장소, 생명 바인드)은 인터페이스만, 실제 구현은
|
|
||||||
`RobloxFactory(BaseModule)`류 백엔드 팩토리가 주입 — `base/bind-system-plan.md`.
|
|
||||||
|
|
||||||
## 최우선 새 열린 질문 (검증 라운드에서 새로 터져나옴)
|
사용자 원 메모: "quad는 register라던가 좀 부정확하거나 느낌이 바로 와닿지
|
||||||
|
않던 용어들이 많음 — 전체적 용어를 보고 생각해볼래? 제안을 줘, 나도 같이
|
||||||
|
볼게." 1차 제안 완료, 아래는 우선순위순 요약 — 최종 판단은 사용자와 계속
|
||||||
|
논의 필요:
|
||||||
|
|
||||||
**전부 확정됨** — 아래 "2026-08-04 3차 라운드" 절 참고. 이 섹션에 새 항목이
|
- **`State`(1순위, 위험도 높음)**: 지금 정의는 "읽기 전용, 파생/캐시 뷰"인데
|
||||||
생기면 여기 추가.
|
React/Vue 등 업계 전반에서 "state"는 거의 항상 "쓸 수 있는 로컬 슬롯"을
|
||||||
|
뜻함 — 처음 보는 사람이 정반대로 오해할 위험이 큼. `Computed`/`Derived`
|
||||||
|
(Vue `computed()`, Svelte 5 `$derived`가 정확히 같은 의미로 씀)가 실제
|
||||||
|
의미에 더 맞아 보임. 단, v1의 "register"를 이미 한 번 "State"로 리네임한
|
||||||
|
지 얼마 안 됐다는 점 고려 필요.
|
||||||
|
- **`DI`(Declarative Instance, 1순위)**: "Dependency Injection"의 업계
|
||||||
|
표준 축약어와 완전히 겹침 — 4차 라운드에서 이미 한 번 실제로 오해가
|
||||||
|
있었던 전례(`base/bind-system-plan.md`의 "인스턴스 생성" 절 참고).
|
||||||
|
- **`PerInstanceState`(2순위)**: 핵심 프리미티브 `State`와 이름이 겹쳐서
|
||||||
|
실제로는 완전히 무관한 유틸(인스턴스별 weak-keyed 저장소)인데 혼동
|
||||||
|
유발 가능 — `PerInstanceStorage`/`InstanceData` 등 대안.
|
||||||
|
- **`Slot`(2순위)**: Vue의 "slot"(콘텐츠 주입 지점)과 이름은 같지만 의미가
|
||||||
|
다름(quad의 Slot은 자식 배열 재조정 프리미티브) — Vue 배경 있는 사람이
|
||||||
|
헷갈릴 수 있음.
|
||||||
|
- **`CreatedRef`/`canExecute`(3순위, 사소함)**: `CreatedRef`는 과거분사형이라
|
||||||
|
생성자처럼 안 읽힘. `canExecute`는 실제로 "이 핸들이 아직 살아있나"
|
||||||
|
확인인데 이름이 범용 권한 체크처럼 들림 — `isAlive` 쪽이 더 직접적.
|
||||||
|
- **이미 지나간 사례로 참고**: `register`(v1) → `State`(v2) 리네임은
|
||||||
|
"모호함"은 풀었지만 "다른 뜻으로 이미 쓰이는 단어"라는 새 문제를 만든
|
||||||
|
셈 — 이번 정리에서 같은 패턴을 조심할 것.
|
||||||
|
- `Store`/`Source`/`Modifier`/`Ref`/`process`/`retract`/`isHandlable`은
|
||||||
|
업계 선례와 잘 맞거나 이미 신중하게 결정된 이름들이라 특별한 문제 없음.
|
||||||
|
|
||||||
## 2026-08-04 4차 라운드 완료 — PA님 실 코드(`initreq/artworks`) 교차검증
|
### 3. 낮은 우선순위
|
||||||
|
|
||||||
사용자가 실제 참고 코드를 공유(`.claude/initreq/artworks/`, PA님 작성) —
|
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
|
||||||
아래 두 항목이 3차 라운드 잠정안에서 정정됨, 나머지는 재검토 후 기존 확정
|
착수를 막지 않음.
|
||||||
유지:
|
- **v1 `objectListClass.__newIndex` 오타 기능의 재현 테스트 필요** —
|
||||||
|
`base/quad-v1-architecture.md`에 남겨진 v1 내부 동작 확인 사항, 마이그레이션
|
||||||
|
가이드 작성 시점에 필요. 지금은 그냥 백로그로만 기록.
|
||||||
|
|
||||||
- **"DI" = Declarative Instance**(Dependency Injection 아님) — 3차 라운드의
|
## 참고: 지금까지 확정된 것 (요약)
|
||||||
오해 정정.
|
|
||||||
- **이벤트 바인딩 정정**: `On.EventName` 도트액세스 안 씀 — PA님 방식(평범한
|
|
||||||
문자열 키 + `ReflectionService` 기반 자동 판별, `Frame { MouseButton1Click
|
|
||||||
= fn }`)으로 전환. Store의 `store.key`는 실질적 타입 이득이 있어 dot-access
|
|
||||||
유지, 이벤트만 예외.
|
|
||||||
- **인스턴스 생성**: 2트랙(`DI.Frame`/`DI.New<<Frame>>`) 대신 PA님 코드처럼
|
|
||||||
제네릭 생성자 함수 하나 + 자주 쓰는 클래스만 정적 필드로 미리 바인딩하는
|
|
||||||
더 단순한 모양으로 정정.
|
|
||||||
- **전파 모델(push-invalidate/pull-recompute)·라이프사이클(GC-native)은
|
|
||||||
재검토 후 기존 확정 유지** — PA님 코드가 반례처럼 보였으나(전자는 push-값
|
|
||||||
단순 pub-sub, 후자는 전부 수동 해제) 대등한 비교가 아니었거나(파생/합성
|
|
||||||
개념 자체가 없음) 지금 필요성이 없다는 게 사용자 판단. 라이프사이클은
|
|
||||||
나중에 하이브리드로 확장 가능한 여지만 기록.
|
|
||||||
- OOP 회피 결정은 PA님의 `class.luau`도 같은 체이닝 상속 보일러플레이트를
|
|
||||||
보여 오히려 보강됨. Instance 태그는 CollectionService 직접 사용 유지.
|
|
||||||
|
|
||||||
→ 상세: `base/bind-system-plan.md`의 "인스턴스 생성 / 이벤트 네이밍
|
전부 `base/`에 문서화되어 더 이상 열려있지 않음 — 상세 근거/논의 과정이
|
||||||
인체공학" 절, "Store/State/Source 온톨로지"의 "PA님 코드와의 교차검증" 절,
|
필요하면 아래 문서를 열어볼 것(라운드별 세부 히스토리는 각 문서 안에
|
||||||
`base/lifecycle-pattern.md`의 "교차검증" 절.
|
"2026-08-04 O차 라운드" 식으로 표시돼 있음):
|
||||||
|
|
||||||
## 2026-08-04 3차 라운드 완료 — dot-access 관습 확정, RobloxFactory 가드 확정 (일부 4차 라운드에서 정정됨)
|
| 주제 | 문서 |
|
||||||
|
|---|---|
|
||||||
- **dot-access를 프로젝트 전역 관습으로 확정**: "정적으로 알려진 것=필드
|
| 전체 아키텍처 결정(디스패치 모델, DOMless, 태그/Ref, Signal 미채택 등) | `base/architecture.md` |
|
||||||
접근, 동적인 것=문자열 호출 폴백"이 Store(`store.key`/`store "key"`)와
|
| Store/State/Source 온톨로지, 인스턴스 생성/이벤트 인체공학, Ref, 남은 API 이름 | `base/bind-system-plan.md` |
|
||||||
인스턴스 생성에 적용됨 — **이벤트는 4차 라운드에서 예외로 정정**(위 참고).
|
| Store 부작용 허용, `:With`+`:Compute`, dot-access 문법 | `base/store-semantics.md` |
|
||||||
- **`RobloxFactory` 재호출 가드 확정**: 같은 팩토리로 재호출 시 무시
|
| 프로바이더 패턴, bind/store 구현 책임 분리 | `base/module-lifecycle-plan.md` |
|
||||||
(no-op, hot-reload 안전), 다른 팩토리로 재호출 시 에러(유일 슬롯 충돌).
|
| Slot 재조정, 재마운트 시 throw, retract=폐기 | `base/slot-plan.md` |
|
||||||
`New()`와는 인스턴스별 테이블 분리로 자연히 공존 — 재설계 불필요. (4차
|
| `Connected`+GC 라이프사이클 패턴 | `base/lifecycle-pattern.md` |
|
||||||
라운드에서 변경 없음)
|
| Modifier(정적 merge, immutable 체이닝, State 필드 지원) | `base/modifier-plan.md` |
|
||||||
|
| 컴포넌트 이식성(전역 store 참조 시 재사용성 문제) | `base/purity-and-effects-plan.md` |
|
||||||
→ 상세: `base/bind-system-plan.md`의 "인스턴스 생성 / 이벤트 네이밍
|
| Fusion/Vide 비교 리서치(주의: 일부 서술은 이후 라운드에서 뒤집힘, 문서 내 정정 표시 참고) | `base/comparison-fusion-vide.md` |
|
||||||
인체공학" 절, "base 유틸은 인터페이스..." 절의 재호출 가드 부분.
|
| v1 내부 동작 스냅샷 | `base/quad-v1-architecture.md` |
|
||||||
|
| 트윈 오버라이드(기본값 Cancel), 세부 옵션만 남음 | `research/tween-plan.md` |
|
||||||
## 2026-08-04 2차 라운드 완료 — Store/State/Source 온톨로지 핵심 메커니즘
|
| quad2-try(폐기된 이전 시도) 리서치 — OOP 상속/커스텀 파서/Slot 스텁/`Pipe` COW 전부 죽은 접근으로 확인, 반복 조사 금지 | `base/bind-system-plan.md` |
|
||||||
|
|
||||||
위 최우선 질문 중 "Store/State/Source 온톨로지 전체"와 "부작용이 slot 생존
|
|
||||||
여부와 어떻게 연관되는가"는 `AskUserQuestion`으로 확인 완료, 더 이상 열려있지
|
|
||||||
않음:
|
|
||||||
|
|
||||||
- **전파 모델**: push-invalidate(신호만, 값 안 실음) / pull-recompute(`Get()`
|
|
||||||
시점에만 재계산) — Fusion식 eager 노드·생성순 정렬은 안 만듦.
|
|
||||||
- **`:Compute`의 self 인자**: raw 값이 아니라 State 핸들 자체를 넘겨서 self도
|
|
||||||
with한 값과 동일하게 lazy하게(`.value`를 실제로 읽을 때만 계산) 처리 —
|
|
||||||
별도 `ComputeWithout` 불필요.
|
|
||||||
- **State는 쓰기 대상이 아님**: `.value`는 읽기 전용, 값 쓰기는 항상 Store의
|
|
||||||
`__newindex`로만. `Source`는 Store 내부 디테일이 아니라 값 하나만 다룰 때
|
|
||||||
쓰는 별도의 가벼운 공개 프리미티브로 격상.
|
|
||||||
- **Slot 생존 확인**: 별도 메커니즘 없이 기존 "생명 바인드 유틸"의
|
|
||||||
`canExecute`로 통일 게이트.
|
|
||||||
- **`store.key` dot-access 타입 추론 제안**: 3차 라운드에서 정식 확정됨(위
|
|
||||||
"2026-08-04 3차 라운드 완료" 절 참고).
|
|
||||||
|
|
||||||
→ 상세: `base/bind-system-plan.md`의 "Store/State/Source 온톨로지 —
|
|
||||||
핵심 메커니즘 확정" 절, `base/store-semantics.md`, `base/lifecycle-pattern.md`.
|
|
||||||
|
|
||||||
## 확정됨 (2026-08-03 질의응답 라운드, 더 이상 열려있지 않음)
|
|
||||||
|
|
||||||
- **Store 책임 분리**: base가 `LifetimeHandle` 추상화 + store-bind의 재실행
|
|
||||||
로직(`process(inst,k,realv)` 재귀)을 소유, provider는 "언제 죽었다고
|
|
||||||
판단할지"(Roblox `Destroying` 등)만 결정. → `base/module-lifecycle-plan.md`,
|
|
||||||
`base/bind-system-plan.md`
|
|
||||||
- **Signal 클래스**: 안 만듦 — 콜백 + `Connected` 계산 속성만. → `base/
|
|
||||||
lifecycle-pattern.md`
|
|
||||||
- **핸들러 계약**: `isHandlable`+`priority`+`process`+`retract` 4종 유지,
|
|
||||||
tbox식 세분화는 지금 안 함. → `base/bind-system-plan.md`
|
|
||||||
- **Ref**: 도입하되 용도는 "id 조회 대체"가 아니라 "외부 관리 instance를
|
|
||||||
점진적으로 마이그레이션/래핑하기 위한 직접 참조 획득". Tween 등 어떤
|
|
||||||
핸들러도 대상 획득에 Ref가 필요하지 않음(항상 `inst`를 직접 받음).
|
|
||||||
→ `base/bind-system-plan.md`
|
|
||||||
- **`retract`(구 cleanup) 호출 시점**: 값 교체 시에만 호출, Destroy 시엔
|
|
||||||
호출 안 함(quad는 자신이 만든 instance의 생명주기 중간에 있지 않으므로
|
|
||||||
destroy-time 정리 자체가 불필요/불가능). → `base/lifecycle-pattern.md`
|
|
||||||
- **핸들러 내부 상태 저장**: base가 범용 weak-keyed per-instance 저장 유틸
|
|
||||||
제공(모든 핸들러 재사용). → `base/bind-system-plan.md`,
|
|
||||||
`base/lifecycle-pattern.md`
|
|
||||||
- **Store 값 설정 문법**: `__newindex`(`myStore.key = v`) 유지, 괄호 생략
|
|
||||||
커링/`:` 체이닝 인체공학도 유지 — 바뀌는 건 내부 구현(팩토리 함수)뿐.
|
|
||||||
→ `base/store-semantics.md`
|
|
||||||
- **Store의 named modifier(`:Add`/`:Mul` 등)**: 안 만듦 — 일반 함수를 받는
|
|
||||||
형태로 통일. → `base/store-semantics.md`
|
|
||||||
|
|
||||||
## 추가 확정됨 (2번째 라운드)
|
|
||||||
|
|
||||||
- **트윈 오버라이드 기본값**: 멈춤(Cancel), 새 트윈은 현재 보간된 값에서 시작.
|
|
||||||
나머지 세 동작(오버라이드/삭제후재시작/끝점이동후재시작)은 옵션으로 선택
|
|
||||||
가능. → `research/tween-plan.md`
|
|
||||||
- **Slot 재마운트 에러**: 즉시 throw. → `base/slot-plan.md`
|
|
||||||
- **`CreatedRef` 콜백 타이밍**: 생성 시점/마운트 시점 둘 다 옵션으로 지원.
|
|
||||||
→ `base/bind-system-plan.md`
|
|
||||||
- **여러 store 값 묶기**: `Store.Combine`류 포지셔널 인자 방식과 Vide식 암묵적
|
|
||||||
추적 둘 다 기각 — `:With(...)` + `:Compute(fn)`(fn은 with한 값을 포지셔널
|
|
||||||
인자가 아니라 클로저로 읽음) 방식으로 확정. Unix 파이프에서 영감받은 완전
|
|
||||||
합성 가능한 State 스트림이 이상향이나 기술적 난이도 미확정 — 과거 시도
|
|
||||||
(`quad2-try/quad-core`) 리서치 진행 중. → `base/bind-system-plan.md`
|
|
||||||
|
|
||||||
## quad2-try(이전 폐기된 시도) 리서치 완료 — 추가 확정
|
|
||||||
|
|
||||||
- **OOP 상속/`--&` 커스텀 파서/Slot 스텁은 확인대로 죽은 접근** — 절대 반복
|
|
||||||
금지, Slot은 from-scratch 설계 그대로 진행(재조사 불필요).
|
|
||||||
- **mutate-vs-`fromState` 긴장 관계**: quad2-try의 `Pipe` copy-on-write
|
|
||||||
절충안(유일한 tip일 때만 뮤테이션, 아니면 복사)이 한때 유력 후보였으나
|
|
||||||
**2026-08-04 검증 라운드에서 사실상 폐기로 재평가됨** — 별도 `Pipe` 타입
|
|
||||||
대신 State 자체가 파이핑 결합체이고 `state(state)`로 분기하는 쪽이 더
|
|
||||||
간단하다는 판단(위 "최우선 새 열린 질문"의 Store/State/Source 온톨로지
|
|
||||||
절로 흡수됨).
|
|
||||||
- **`Depend(...)` 액션, `:With` 네이밍**은 이전 시도에서도 지향했던 것과 일치
|
|
||||||
— 그대로 채택. → `base/bind-system-plan.md`
|
|
||||||
|
|
||||||
## 순수성/이식성, 기존 인스턴스 바인드 — 확인 완료, 낮은 우선순위로 유지
|
|
||||||
|
|
||||||
- **"순수함수" 문제는 실제로는 "이식성" 문제였음** — 재사용 의도 컴포넌트가
|
|
||||||
전역 store를 직접 참조하면 이식성이 깨짐(단일 페이지용 컴포넌트나 라이브러리
|
|
||||||
내부 전용 공유 상태는 문제 없음). 기술적 강제 안 함, 문서 경고 수준으로
|
|
||||||
확정. → `research/purity-and-effects-plan.md`
|
|
||||||
- **이미 생성된 인스턴스 재바인드**: 실제 요청한 사용자를 본 적 없지만
|
|
||||||
`retract` 인프라가 이미 있어 미래에 자연스럽게 가능해질 여지가 있음 —
|
|
||||||
"미지원" 확정도, 착수도 안 함, 진짜 열린 가능성으로만 유지. → `research/
|
|
||||||
existing-instance-bind-plan.md`
|
|
||||||
|
|
||||||
## 급하지 않음, 여유 있을 때만
|
|
||||||
|
|
||||||
- 태그 시스템의 네임스페이싱 부재(라이브러리 간 충돌 가능성)를 얼마나
|
|
||||||
심각하게 볼지 — 지금은 "별도 네임스페이스 개념은 복잡도 대비 이득이 적다"는
|
|
||||||
판단으로 보류 중. → `base/architecture.md` 5번 항목.
|
|
||||||
- Store가 Store를 담는 경우 이중 해제(double-dispose) 방지가 실제로 필요한
|
|
||||||
상황이 있는지 — 구현 단계에서 실사례로 재검증. → `base/bind-system-plan.md`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
전체 순서/우선순위는 루트 `CLAUDE.md`가 최종 소스 — 위 표는 힌트일 뿐 그쪽이
|
전체 순서/우선순위는 루트 `CLAUDE.md`가 최종 소스 — 위 표는 힌트일 뿐 그쪽이
|
||||||
|
|
|
||||||
110
.claude/research/component-composition-plan.md
Normal file
110
.claude/research/component-composition-plan.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# 컴포넌트화 (Roblox 기본 오브젝트 이외의 사용자 정의 컴포넌트)
|
||||||
|
|
||||||
|
**상태**: research — 2026-08-04 세션 채팅 논의에서 핵심 골격 수렴, 세부
|
||||||
|
API 이름/modifier·Ref passthrough는 미정. 사용자가 "지금 quad에서 가장
|
||||||
|
문제되는 부분"으로 직접 지목한 주제. `base/bind-system-plan.md`의
|
||||||
|
Store/State/Source 온톨로지가 먼저 확정된 뒤에야 이 논의가 열림 — 그
|
||||||
|
문서가 선행 컨텍스트.
|
||||||
|
|
||||||
|
## 문제
|
||||||
|
|
||||||
|
v1의 `Class.Extend()`(Init/Render/AfterRender/Getter/Setter/UpdateTriggers,
|
||||||
|
`base/quad-v1-architecture.md` 참고)는 이미 OOP 상속 스타일이라 폐기
|
||||||
|
방향이지만, 그게 제공하던 실제 편의 기능(컴포넌트가 자기 store를 자동으로
|
||||||
|
가짐, props로 넘어온 State를 자동 흡수, `self:Default`/`self "key"`로
|
||||||
|
기본값·바인딩)까지 같이 버려도 되는지가 미결이었음. `MyComp {...}` 형태로
|
||||||
|
호출되는 사용자 정의 컴포넌트를 v2에서 어떤 모양으로 작성하게 할지가 핵심
|
||||||
|
질문.
|
||||||
|
|
||||||
|
## v1 실제 메커니즘 (조사 완료, `quad.qwreey.kr` 튜토리얼 + `initreq/quad/src/` 소스로 교차검증)
|
||||||
|
|
||||||
|
- `myStore "key"` → register(현재 State에 해당) 반환. `:Default(v)`/
|
||||||
|
`:With(fn)`/`:Add(v)`/`:Tween(opts)` 체이닝 가능(`store.lua:433-457`).
|
||||||
|
- `Class.Extend()`의 `:Init(props)`에서 `props:Default("Size", v)`로 기본값
|
||||||
|
설정, props 테이블 자체가 store 인스턴스로 변신(`class.lua:365-379`,
|
||||||
|
`storeNew(prop,nil)`).
|
||||||
|
- props로 넘어온 값이 State(`quad_register`)면 `initStoreRegisterBinding`
|
||||||
|
(`store.lua:394-431`)이 자동으로 감지해 컴포넌트 자신의 store 키에 재귀
|
||||||
|
연결 — **자동 흡수 매직**이 실제로 존재했음.
|
||||||
|
- `self(name)` linker가 **두 가지 역할**을 겸함: (1) `self "_button"`을
|
||||||
|
자식 자리에 넣으면 렌더링된 인스턴스를 `self._button`에 즉시 잡아둠(Ref
|
||||||
|
역할) (2) `[Event.Prop "Text"] = self "Text"`로 인스턴스 프로퍼티 변경을
|
||||||
|
다시 컴포넌트 store로 역방향 전파(양방향 바인딩, `EmitPropertyChangedSignal`
|
||||||
|
자동 연결과 동일) — quad.qwreey.kr 튜토리얼 `11_extend/` 문서 원문 확인.
|
||||||
|
|
||||||
|
이 두 역할이 v2 온톨로지에서는 이미 갈라져 있음: (1)은 확정된 **Ref**가
|
||||||
|
대체, (2)는 이번 논의에서 다루는 Source 양방향 프록시가 대체.
|
||||||
|
|
||||||
|
## 수렴된 결론
|
||||||
|
|
||||||
|
### 1. 컴포넌트 = 그냥 함수, "자기 store 자동 소유" 매직은 폐기
|
||||||
|
|
||||||
|
`MyComp = function(props) return Frame {...} end`, 호출 규약은
|
||||||
|
`Frame{...}`와 동일(`MyComp{...}` → `MyComp(propsTable)`). v1의 Extend
|
||||||
|
자동-store-생성+자동-흡수 매직은 재현하지 않음 — 대신 React식으로 호출부가
|
||||||
|
State/raw/Source/콜백 중 뭘 넘길지 명시적으로 고름. 이유: 자동 흡수는
|
||||||
|
매 컴포넌트 호출마다 "이 prop이 State인가?" 타입 분기를 프레임워크가
|
||||||
|
암묵적으로 수행해야 하는 매직이고, 명시적 전달이 더 단순·예측 가능(React가
|
||||||
|
Vue/Svelte 대비 내세우는 강점과 동일 논리) — **사용자 확정**("마법 안쓴다
|
||||||
|
그것도 동의함").
|
||||||
|
|
||||||
|
### 2. State/Source 경계 규칙: 파생이면 읽기전용, 원본이면 쓰기 가능
|
||||||
|
|
||||||
|
State는 `:With`/`:Compute`로 만들어진 파생값일 수 있어 쓰기가 정의 자체가
|
||||||
|
안 됨. Source(독립이든 Store 소속 `StoreSource` 프록시든)는 파생이 아니라
|
||||||
|
항상 원본 슬롯 하나를 직접 가리키므로 쓰기가 의미 있음 — **사용자 확정**
|
||||||
|
("맞음. 확실해").
|
||||||
|
|
||||||
|
### 3. `StoreSource`: Source를 인터페이스+구현체로 두고, Store 키에서 그 인터페이스를 구현하는 얇은 프록시를 받음
|
||||||
|
|
||||||
|
- **Source = 인터페이스이자 구현체**: 독립 생성자 `Source(initial)`가 기본
|
||||||
|
구현체, `store:GetSource("key")`(가칭)류 접근자가 반환하는 값은 같은
|
||||||
|
인터페이스를 구현하는 별도의 얇은 프록시(`StoreSource`) — 읽기는
|
||||||
|
`store.key`로, 쓰기는 `store.key = v`로 위임. **내부 Source 객체를 그대로
|
||||||
|
노출하지 않음** — 그러면 "쓰기는 오직 Store의 `__newindex`뿐"이라는 기존
|
||||||
|
확정과 새 쓰기 경로가 충돌하게 됨.
|
||||||
|
- **캐시 안 함**: State가 이미 "매번 새로 만듦, store에 캐시 안 됨"으로
|
||||||
|
확정돼 있어 일관성 + 엔지니어링 비용 둘 다 이쪽이 쌈 — **사용자 확정**
|
||||||
|
("그냥 엔지니어링적으로 비용이 싼거 택해").
|
||||||
|
|
||||||
|
### 4. Source 직접 전달(양방향)은 핸들러 계약 확장 없이 타입 유니온으로 처리 — 단, 실사용 범위는 좁음
|
||||||
|
|
||||||
|
- 핸들러가 값을 받을 때 `Source<T> | State<T>` 유니온으로 받고, 내부에서
|
||||||
|
타입 체크만 하면 됨(Source면 인스턴스 변경 이벤트에 걸어 역방향 쓰기까지
|
||||||
|
처리, State면 읽기만) — `isHandlable`/`priority`/`process`/`retract` 4종
|
||||||
|
계약에 5번째 항목을 추가할 필요 없음. Source 자체가 계산이 없는 원천이라
|
||||||
|
가능한 단순화 — **사용자 확정**("그냥 타입 상 source를 받거나 state를
|
||||||
|
받거나 하면 됨. source 자체는 원천이라 컴퓨팅 같은거 없어").
|
||||||
|
- **하지만 실사용은 좁을 것으로 예상**: `isEnabled`처럼 여러 조건에 영향
|
||||||
|
받는(=파생된) 값은 애초에 State지 Source가 아니므로 이 경로로 못 넘김.
|
||||||
|
즉 Source 직접 전달이 통하는 건 진짜 단순한 1:1 원본-토글 케이스뿐이고,
|
||||||
|
일반적인 경우엔 React식 `value(State) + onChange(callback)` 패턴이 기본
|
||||||
|
— **사용자 확정**("isenabled가 여러 조건에 영향 받으면 바로 문제가
|
||||||
|
생기는거지. 따라서 실제 사용은 제한적일듯. callback을 쓰는게
|
||||||
|
일반적이여 보이긴 해. 타입으로도 편하기도 하고 디버깅도 편함").
|
||||||
|
|
||||||
|
### 5. 리프(Roblox 프로퍼티) 바인딩엔 원칙적으로 State만
|
||||||
|
|
||||||
|
계산된 최종값만 실제로 인스턴스에 반영되어야 하므로, 리프 바인딩은 State가
|
||||||
|
일반 경로. Source는 리프 바인딩용 프리미티브가 아니라, 아주 단순한 구조에서
|
||||||
|
콜백 보일러플레이트를 줄이기 위한 좁은 용도의 예외 — **사용자 확정**
|
||||||
|
("리프 바인딩엔 state만 쓰이지 않을까... source는 그냥 아주 단순한
|
||||||
|
구조에서 콜백을 넣고 하는 복잡함을 줄이기 위함일 뿐임").
|
||||||
|
|
||||||
|
## 아직 열린 질문 (`.claude/question.md`에도 취합)
|
||||||
|
|
||||||
|
- **modifier/Ref가 컴포넌트 경계를 어떻게 통과하는가**: 컴포넌트가 플레인
|
||||||
|
함수이고 반환하는 루트가 여러 개(혹은 Slot으로 갈라지는 구조)일 때, 호출부가
|
||||||
|
넘긴 modifier/Ref가 "어느 루트로 가야 하는지" 모호해지는 케이스가 있음.
|
||||||
|
Jetpack Compose는 언어 강제가 아니라 "컴포저블은 `modifier` 파라미터를
|
||||||
|
받아 루트에 적용해야 한다"는 순수 관례(+린트)로 풂 — quad도 비슷한 관례
|
||||||
|
기반으로 갈 수 있어 보이나 다중 루트 케이스는 미정. **주의: 이건 "경계를
|
||||||
|
어떻게 통과하는가"의 문제이고, "modifier 값 자체가 어떻게 동작하는가"(정적
|
||||||
|
merge, immutable 체이닝)는 `research/modifier-plan.md`로 이미 별도 확정됨
|
||||||
|
— 둘을 혼동하지 말 것.**
|
||||||
|
- **정확한 API 이름**: `Component`(플레인 함수 규약이라 별도 래퍼가 필요한지
|
||||||
|
자체도 불확실 — 아마 불필요), `GetSource` 계열 접근자 이름, `Source`
|
||||||
|
독립 생성자 이름은 전부 가칭. `base/bind-system-plan.md`의 "남은 열린
|
||||||
|
질문" 절(정확한 함수/생성자 이름 미정)과 같은 급의 후순위 항목.
|
||||||
|
- **`quad2-try`는 확인 불필요로 재확인** — 진행이 중단된 상태라 이 논의와
|
||||||
|
무관.
|
||||||
|
|
@ -6,12 +6,12 @@
|
||||||
## 문제
|
## 문제
|
||||||
|
|
||||||
이미 생성된 Roblox Instance에 새로운 `{k=v}` 프롭 테이블을 나중에 바인드하는
|
이미 생성된 Roblox Instance에 새로운 `{k=v}` 프롭 테이블을 나중에 바인드하는
|
||||||
걸 허용할지. 허용하려면 이전 바인드를 끊는 처리가 필요한데, cleanup이
|
걸 허용할지. 허용하려면 이전 바인드를 끊는 처리가 필요한데, retract가
|
||||||
구현되어 있어도 바로 지원하는 건 엔지니어링 비용이 높음.
|
구현되어 있어도 바로 지원하는 건 엔지니어링 비용이 높음.
|
||||||
|
|
||||||
## 기울어진 방향
|
## 기울어진 방향
|
||||||
|
|
||||||
**UB로 두거나, 마일스톤(추후 구현)으로 미룬다.** cleanup이 이미 있고 store
|
**UB로 두거나, 마일스톤(추후 구현)으로 미룬다.** retract가 이미 있고 store
|
||||||
바인드도 우선순위 높은 플러그라면 이론적으로는 가능해 보이지만(핸들러
|
바인드도 우선순위 높은 플러그라면 이론적으로는 가능해 보이지만(핸들러
|
||||||
레지스트리가 이미 "우선순위 스캔 후 bind" 구조라 재바인드도 같은 경로를 타면
|
레지스트리가 이미 "우선순위 스캔 후 bind" 구조라 재바인드도 같은 경로를 타면
|
||||||
됨), 초기 구현에서 **우선순위를 낮게** 잡아야 함 — 문제 유무가 많을 수 있어서.
|
됨), 초기 구현에서 **우선순위를 낮게** 잡아야 함 — 문제 유무가 많을 수 있어서.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
# Tween / 애니메이션 플러깅 (착수 전, 사용자와 상의 필요)
|
# Tween / 애니메이션 플러깅 (기본값 확정, 옵션 키 이름만 남음)
|
||||||
|
|
||||||
**상태**: research — 방향은 뚜렷하게 잡혀 있으나(라이브러리가 트윈을 직접
|
**상태**: research — 방향은 뚜렷하게 잡혀 있고(라이브러리가 트윈을 직접
|
||||||
구현하지 않는다) cleanup 순서/오버라이드 시맨틱은 미확정. 원본:
|
구현하지 않는다), `retract` 순서/오버라이드 기본값(Cancel)도 확정됨. 남은 건
|
||||||
|
기본값 외 나머지 오버라이드 동작을 고르는 옵션 키의 정확한 이름/시그니처
|
||||||
|
정도. 원본:
|
||||||
`.claude/initreq/raw-userinput.md` "트윈은 어떻게 할 것이냐" / "스토어 값은
|
`.claude/initreq/raw-userinput.md` "트윈은 어떻게 할 것이냐" / "스토어 값은
|
||||||
항상 먼저 캐치한다" / "네임스페이스드 객체" 절. Fusion의 Tween/Spring이
|
항상 먼저 캐치한다" / "네임스페이스드 객체" 절. Fusion의 Tween/Spring이
|
||||||
반응 그래프 안에 있는 설계는 명시적 반면교사 — `base/comparison-fusion-vide.md`
|
반응 그래프 안에 있는 설계는 명시적 반면교사 — `base/comparison-fusion-vide.md`
|
||||||
|
|
@ -79,7 +81,7 @@ Destroy 시점엔 아무 것도 안 함(라이프타임 `Connected` 체크로
|
||||||
## 네임스페이스드 객체 (성능상 이유로 보류)
|
## 네임스페이스드 객체 (성능상 이유로 보류)
|
||||||
|
|
||||||
트윈 대상을 이름으로 찾는 별도 네임스페이스는 성능상 별로라고 판단 —
|
트윈 대상을 이름으로 찾는 별도 네임스페이스는 성능상 별로라고 판단 —
|
||||||
TagService를 쓰는 게 나아 보이지만, 트윈 전용 네임스페이스가 따로 있을
|
CollectionService를 쓰는 게 나아 보이지만, 트윈 전용 네임스페이스가 따로 있을
|
||||||
필요가 있는지는 미정(단, 위 정정으로 이 절 자체의 필요성이 낮아짐 — 핸들러가
|
필요가 있는지는 미정(단, 위 정정으로 이 절 자체의 필요성이 낮아짐 — 핸들러가
|
||||||
이미 대상을 직접 받으므로 "나중에 이름으로 찾아서 트윈"할 필요 자체가 잘
|
이미 대상을 직접 받으므로 "나중에 이름으로 찾아서 트윈"할 필요 자체가 잘
|
||||||
없을 수 있음).
|
없을 수 있음).
|
||||||
|
|
|
||||||
177
CLAUDE.md
177
CLAUDE.md
|
|
@ -20,48 +20,33 @@ Roblox 엔진에서 동작하는 DOMless UI 렌더러 **quad**를 처음부터
|
||||||
길게 잡음.
|
길게 잡음.
|
||||||
|
|
||||||
**지금은 설계/계획 단계이고 구현은 아직 시작 전** — 저장소 루트에 실제 소스
|
**지금은 설계/계획 단계이고 구현은 아직 시작 전** — 저장소 루트에 실제 소스
|
||||||
코드(`src/` 등)가 없음. 2026-08-03에 확정됐던 핵심 아키텍처 결정들(Store
|
코드(`src/` 등)가 없음. 핵심 아키텍처(Store 책임 분리, `process`/`retract`
|
||||||
책임 분리, `process`/`retract` 디스패치 모델, Signal 미채택, Ref 역할, Store
|
디스패치 모델, Store/State/Source 온톨로지, 소스 트리 구조, Modifier 메커니즘,
|
||||||
문법 인체공학, 트윈 기본 오버라이드, Slot 재마운트 에러 처리, 순수성→이식성
|
컴포넌트=플레인 함수)는 전부 `.claude/base/`에 문서로 확정돼 있음 — 먼저
|
||||||
재정의 등)은 2026-08-04에 `AskUserQuestion`으로 하나씩 재검증까지 마쳐서
|
`.claude/base/architecture.md`를 읽을 것. **단, "핵심 설계 질문이 더 이상
|
||||||
확정 상태 — `.claude/question.md`의 "확정됨" 절 참고. 이전에 시도했다 폐기한
|
없다"는 뜻은 아님** — 컴포넌트화(특히 modifier/Ref가 컴포넌트 경계를 어떻게
|
||||||
v2 재작성 시도(`.claude/initreq/quad2-try`)도 리서치 완료 — OOP 상속/커스텀
|
통과하는지)는 사용자가 직접 "지금 quad에서 가장 문제되는 부분"으로 지목한
|
||||||
파서/Slot 스텁은 확인된 죽은 접근이라 반복 금지, `Pipe`의 copy-on-write
|
채 아직 열려있음, 아래 "지금 할 일" 참고.
|
||||||
절충안은 한때 살려볼 후보였으나 **2026-08-04에 사실상 폐기로 재평가**됨(State
|
|
||||||
자체가 `state(state)`로 분기하는 쪽으로 대체).
|
|
||||||
|
|
||||||
**Store/State/Source 온톨로지 및 관련 인체공학 질문은 2026-08-04 네 라운드에
|
이전에 시도했다 폐기한 v2 재작성 시도(`.claude/initreq/quad2-try`)도 리서치
|
||||||
걸쳐 전부 확정됨**(사용자가 공유해준 실제 참고 코드 `.claude/initreq/
|
완료 — OOP 상속/커스텀 파서/Slot 스텁/`Pipe` copy-on-write 절충안은 확인된
|
||||||
artworks/`, PA님 작성, 로 4차 교차검증까지 마침) — push-invalidate/
|
죽은 접근이라 반복 조사 금지(`base/bind-system-plan.md` 참고).
|
||||||
pull-recompute 전파 모델, `:Compute` self/with 인자를 둘 다 lazy State
|
|
||||||
핸들로 통일, State는 쓰기 불가(값 쓰기는 항상 Store의 `__newindex`),
|
|
||||||
`Source`는 Store와 별개인 독립 프리미티브로 격상, Slot 생존 확인은 기존
|
|
||||||
canExecute 유틸 재사용으로 해소, `store.key` dot-access를 타입 추론 1급
|
|
||||||
경로로(인스턴스 생성도 같은 관습, 단 이벤트는 PA님 방식인 평범한 문자열
|
|
||||||
키+런타임 리플렉션으로 예외), `RobloxFactory` 재호출 가드(같은 팩토리=무시,
|
|
||||||
다른 팩토리=에러)까지 확정. 남은 건 정확한 API 표면 이름뿐 —
|
|
||||||
`.claude/base/bind-system-plan.md` 전체, `.claude/question.md`의
|
|
||||||
"2026-08-04" 절들 참고.
|
|
||||||
|
|
||||||
**소스 트리 구조도 확정됨(2026-08-04 5차 라운드)**: `bind-system-plan.md`/
|
|
||||||
`module-lifecycle-plan.md`/`slot-plan.md` 모두 `research/`에서 `base/`로
|
|
||||||
승격 완료. 모노레포(`quad-base`/`quad-roblox` 서브폴더, RbxUtil 패턴)로
|
|
||||||
당장은 모놀리식 진행, 패키지 경계(디스패치 엔진까지 base가 인터페이스로
|
|
||||||
소유)까지 확정 — `base/architecture.md`의 "구현 착수: 소스 트리 구조 확정"
|
|
||||||
절 참고. 아래 "지금 할 일" 1번이 다음 단계(실제 스캐폴딩)를 명시.
|
|
||||||
|
|
||||||
## 계획 문서 구조
|
## 계획 문서 구조
|
||||||
|
|
||||||
`.claude/README.md`가 색인. 요약:
|
`.claude/README.md`가 색인. 요약:
|
||||||
- `.claude/base/` — 확정된 아키텍처/컨텍스트, plan/done 개념 없음. 먼저
|
- `.claude/base/` — 확정된 아키텍처/컨텍스트, plan/done 개념 없음. 먼저
|
||||||
`.claude/base/architecture.md`를 읽을 것.
|
`.claude/base/architecture.md`를 읽을 것.
|
||||||
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의.
|
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의. 지금은
|
||||||
|
`tween-plan.md`(세부 옵션만 남음), `existing-instance-bind-plan.md`(급하지
|
||||||
|
않음), `component-composition-plan.md`(**사용자가 최우선으로 지목한 열린
|
||||||
|
주제**) 세 개뿐.
|
||||||
- `.claude/qa-request/`, `.claude/archive/`, `.claude/feedback/` — 구현
|
- `.claude/qa-request/`, `.claude/archive/`, `.claude/feedback/` — 구현
|
||||||
시작되면 쓰기 시작함, 지금은 비어있음.
|
시작되면 쓰기 시작함, 지금은 비어있음.
|
||||||
- `.claude/initreq/` — 클론해둔 참고 레포(quad v1, Fusion, Vide, rbvm, tbox,
|
- `.claude/initreq/` — 클론해둔 참고 레포(quad v1, Fusion, Vide, rbvm, tbox,
|
||||||
code-docker) + 원본 요청. **읽기 전용, `.gitignore`로 커밋 제외됨** — 내용을
|
code-docker) + PA님 실 코드(`artworks/`) + 원본 요청. **읽기 전용,
|
||||||
다른 곳으로 옮기지 말고 항상 원본 그대로 둘 것. 리서치가 더 필요하면 이
|
`.gitignore`로 커밋 제외됨** — 내용을 다른 곳으로 옮기지 말고 항상 원본
|
||||||
폴더를 다시 파고들 것.
|
그대로 둘 것. 리서치가 더 필요하면 이 폴더를 다시 파고들 것.
|
||||||
- `.claude/question.md` — 사용자가 답해야 할 질문 전체 취합(우선순위순).
|
- `.claude/question.md` — 사용자가 답해야 할 질문 전체 취합(우선순위순).
|
||||||
- 루트 `HUMAN_TODO.md` — 사람만 할 수 있는 일(로컬 GUI 조작, 스케줄/루프
|
- 루트 `HUMAN_TODO.md` — 사람만 할 수 있는 일(로컬 GUI 조작, 스케줄/루프
|
||||||
설정 등).
|
설정 등).
|
||||||
|
|
@ -72,7 +57,9 @@ canExecute 유틸 재사용으로 해소, `store.key` dot-access를 타입 추
|
||||||
컨텍스트 보호. 이미 완료된 v1/rbvm/tbox/Fusion/Vide 리서치 결과는
|
컨텍스트 보호. 이미 완료된 v1/rbvm/tbox/Fusion/Vide 리서치 결과는
|
||||||
`.claude/base/`에 정리되어 있으니 중복 조사하지 말고 먼저 그걸 볼 것.
|
`.claude/base/`에 정리되어 있으니 중복 조사하지 말고 먼저 그걸 볼 것.
|
||||||
- **병렬화 가능한 작업은 Agent 여러 개를 한 메시지에 동시 호출.** 서로 독립적인
|
- **병렬화 가능한 작업은 Agent 여러 개를 한 메시지에 동시 호출.** 서로 독립적인
|
||||||
파일/주제를 다루는 리서치나 구현 조사가 여기 해당.
|
파일/주제를 다루는 리서치나 구현 조사, 또는 서로 다른 문서 파일을 고치는
|
||||||
|
문서 정리 작업이 여기 해당(단, 같은 파일을 동시에 고치는 에이전트를 병렬로
|
||||||
|
띄우지 말 것 — 충돌함).
|
||||||
- **크리티컬한 설계 결정은 구현으로 밀어붙이지 말고 plan을 research/에 남긴 채
|
- **크리티컬한 설계 결정은 구현으로 밀어붙이지 말고 plan을 research/에 남긴 채
|
||||||
연기.** 사용자는 Lua/Roblox 엔진을 깊이 아는 사람 — 근거와 선택지를 문서에
|
연기.** 사용자는 Lua/Roblox 엔진을 깊이 아는 사람 — 근거와 선택지를 문서에
|
||||||
정리해두면 사용자가 깨어있을 때 훑어보고 답해줄 것. `.claude/question.md`에
|
정리해두면 사용자가 깨어있을 때 훑어보고 답해줄 것. `.claude/question.md`에
|
||||||
|
|
@ -81,6 +68,12 @@ canExecute 유틸 재사용으로 해소, `store.key` dot-access를 타입 추
|
||||||
조사하게 되는 재작업을 막기 위함. `.claude/base/`로 승격, `.claude/qa-request/`로
|
조사하게 되는 재작업을 막기 위함. `.claude/base/`로 승격, `.claude/qa-request/`로
|
||||||
이동, 또는 문서 자체를 갱신. code-docker/webmanager의 `.claude/` 관리 방식이
|
이동, 또는 문서 자체를 갱신. code-docker/webmanager의 `.claude/` 관리 방식이
|
||||||
좋은 예시(`.claude/initreq/code-docker/webmanager/.claude/README.md` 참고).
|
좋은 예시(`.claude/initreq/code-docker/webmanager/.claude/README.md` 참고).
|
||||||
|
- **문서가 쌓이면서 모순/중복/stale 마커가 생기기 쉬움 — 주기적으로 감사할
|
||||||
|
것.** 2026-08-04 세션에 실제로 전체 `.claude/` 코퍼스에서 이런 문제가
|
||||||
|
다수 발견되어 정리함(아래 "최근 세션 요약" 참고) — 여러 라운드에 걸쳐
|
||||||
|
같은 문서를 계속 고치다 보면 "정정됨" 표시가 원래 문장에 안 반영되고
|
||||||
|
방치되는 패턴이 반복되니, 큰 방향 전환이 있을 때마다 관련 문서 전체를
|
||||||
|
훑어 확인할 것.
|
||||||
- **Roblox Studio MCP 연결 시 주의**: Studio는 잘 죽는 편 — 죽었을 때 살리려고
|
- **Roblox Studio MCP 연결 시 주의**: Studio는 잘 죽는 편 — 죽었을 때 살리려고
|
||||||
위험한 명령을 반복 시도하지 말 것. 그런 상황이면 MCP 없이 할 수 있는 작업만
|
위험한 명령을 반복 시도하지 말 것. 그런 상황이면 MCP 없이 할 수 있는 작업만
|
||||||
하거나 대기. 연결 방법은 `HUMAN_TODO.md` 1번 항목 참고(사용자가 Studio에서
|
하거나 대기. 연결 방법은 `HUMAN_TODO.md` 1번 항목 참고(사용자가 Studio에서
|
||||||
|
|
@ -94,77 +87,59 @@ canExecute 유틸 재사용으로 해소, `store.key` dot-access를 타입 추
|
||||||
|
|
||||||
## 지금 할 일 (우선순위순)
|
## 지금 할 일 (우선순위순)
|
||||||
|
|
||||||
1. **[다음 세션 최우선] 실제 스캐폴딩.** 소스 트리 구조는 문서로 이미 확정됨
|
1. **컴포넌트화 논의 계속 — 사용자가 직접 "가장 문제되는 부분"으로 지목.**
|
||||||
(`base/architecture.md`의 "구현 착수: 소스 트리 구조 확정" 절) — 다음
|
`research/component-composition-plan.md` 참고. 핵심 골격(컴포넌트=플레인
|
||||||
세션에서 실제로 `quad-base/`, `quad-roblox/` 폴더, 각각의 `wally.toml`,
|
함수, State/Source 읽기·쓰기 경계, `StoreSource` 프록시)과 Modifier
|
||||||
루트 `default.project.json`, `.luaurc`를 만들 것. 이 시점부터 `qa-request/`/
|
메커니즘 자체(`base/modifier-plan.md`, 완전 확정)는 수렴됨 — 남은 건
|
||||||
`archive/` 폴더가 실제로 쓰이기 시작함.
|
**modifier/Ref가 컴포넌트 경계(특히 다중 루트)를 어떻게 통과하는가**라는
|
||||||
2. 남은 세부 시그니처(`CreatedRef`/`state()`/`Source()`/`DI`류 정확한
|
진짜 설계 질문(이름 문제가 아님). 다음 세션에서 이걸 이어서 파고들 것.
|
||||||
이름)는 위 항목과 자연스럽게 같이 확정 가능 — PA님 실 코드(`.claude/
|
2. **실제 스캐폴딩.** 소스 트리 구조는 문서로 이미 확정됨(`base/
|
||||||
initreq/artworks/`)를 이미 받아서 교차검증 완료(아래 인수인계 메모
|
architecture.md`의 "구현 착수: 소스 트리 구조 확정" 절) — `quad-base/`,
|
||||||
참고), `On` 모듈은 이벤트 바인딩 방식이 바뀌며 아예 불필요해짐.
|
`quad-roblox/` 폴더, 각각의 `wally.toml`, 루트 `default.project.json`,
|
||||||
3. `research/existing-instance-bind-plan.md`는 급하지 않음 — 스코프 논의만
|
`.luaurc`를 만들 것. 1번의 컴포넌트 경계 논의가 `DI`/`Modifier` 모듈
|
||||||
|
설계에 영향을 주므로, 그 결론이 안 나온 상태에서도 나머지 구조(Store/
|
||||||
|
State/Source, 디스패치 엔진, Slot)는 그대로 스캐폴딩 가능 — 막을 필요
|
||||||
|
없음(`architecture.md`에도 명시). 이 시점부터 `qa-request/`/`archive/`
|
||||||
|
폴더가 실제로 쓰이기 시작함.
|
||||||
|
3. **용어 정리 — 사용자가 별도로 요청, 진행 중.** "register"(v1) 같이
|
||||||
|
부정확한 이름들을 전체적으로 재검토하자는 요청 — 1차 제안 완료(우선순위
|
||||||
|
순: `State`가 React/Vue식 "쓸 수 있는 로컬 상태"라는 통상 의미와 반대라
|
||||||
|
가장 위험, `DI`가 Dependency Injection 축약어와 충돌, `PerInstanceState`가
|
||||||
|
핵심 프리미티브 `State`와 이름 충돌 — 세부는 `.claude/question.md` 참고),
|
||||||
|
사용자와 같이 계속 논의 필요.
|
||||||
|
4. `research/existing-instance-bind-plan.md`는 급하지 않음 — 스코프 논의만
|
||||||
필요, 구현 착수를 막지 않음.
|
필요, 구현 착수를 막지 않음.
|
||||||
4. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
|
5. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
|
||||||
(`HUMAN_TODO.md` 2번 항목).
|
(`HUMAN_TODO.md` 2번 항목).
|
||||||
|
|
||||||
## 인수인계 메모 (2026-08-04 세션 종료 시점, 5차 라운드까지 반영)
|
## 최근 세션 요약 (2026-08-04, 6차 라운드 이후)
|
||||||
|
|
||||||
**5차 라운드(소스 구조 확정)**: 4차 라운드 종료 시점에 서브에이전트로 먼저
|
**6차 라운드**: 남아있던 "급하지 않음" 질문 두 개 해소 — 태그 네임스페이싱
|
||||||
계획 문서 전체의 정합성을 점검(차질 없음 확인) 후 진행. 패키징 방식은
|
충돌은 컴포넌트 단위로는 Ref가 대신 해결해줘서 심각하게 안 봄(`architecture.md`
|
||||||
서브에이전트 웹 리서치로 확인(`.luaurc` alias 런타임 미지원, wally 심볼릭
|
5번), Store가 Store를 담는 경우는 없음으로 확정(Store는 Source에 준하는
|
||||||
링크/타입 문제, `Sleitnick/RbxUtil`의 모노레포+개별 wally.toml 선례,
|
"시작점"이라 다른 반응형 값에 자동 연결되지 않음, `bind-system-plan.md`).
|
||||||
`pesde`는 아직 이름) — 모노레포로 당장 진행, 나중에 실제 분리 결정.
|
|
||||||
패키지 경계는 사용자가 "base=인터페이스, roblox=구현"이라는 원칙을 명확히
|
|
||||||
해서 확정 — Store/State/Source 온톨로지뿐 아니라 `process`/`retract`
|
|
||||||
디스패치 엔진, `LifetimeHandle`/`PerInstanceState` 인터페이스, Ref, Slot
|
|
||||||
코어 재조정 로직까지 전부 `quad-base`가 소유(다른 엔진에서도 재사용
|
|
||||||
가능해야 한다는 전제, 엔진마다 큰 구현 중복 방지가 목적). Slot도 같은
|
|
||||||
원칙 적용 확정, 그 과정에서 `k:number,v:Instance` 중첩 인스턴스 자식용
|
|
||||||
`InstanceChild` 핸들러가 추가로 필요하다는 게 밝혀짐. `bind-system-plan.md`/
|
|
||||||
`module-lifecycle-plan.md`/`slot-plan.md` 세 문서 모두 `research/`에서
|
|
||||||
`base/`로 승격 완료, `base/architecture.md`에 전체 소스 트리가 문서화됨 —
|
|
||||||
실제 폴더/파일 스캐폴딩은 다음 세션(위 "지금 할 일" 1번).
|
|
||||||
|
|
||||||
## 인수인계 메모 (2026-08-04 세션 종료 시점, 4차 라운드까지 반영)
|
**그 이후 채팅에서 세 가지 큰 스레드가 새로 열림/정리됨**:
|
||||||
|
- **Modifier 메커니즘 전체 확정** — 런타임 pluggable 핸들러가 아니라 정적
|
||||||
|
merge, immutable+`table.clone` 기반 체이닝, 필드가 State일 수도 있는
|
||||||
|
경우의 setter/getter 동작까지 전부 확정(`base/modifier-plan.md`, 새로
|
||||||
|
base 승격). 이 논의에서 "관측해야 실체화된다"는 프로젝트 전역 원칙도
|
||||||
|
명문화(`bind-system-plan.md`).
|
||||||
|
- **컴포넌트화 논의 시작, 아직 미완** — v1의 `Class.Extend()` 자동-store
|
||||||
|
매직은 폐기하고 React식으로 값을 명시적으로 전달하는 방향으로 수렴,
|
||||||
|
`StoreSource`(Source를 인터페이스+구현체로 보고 Store 키에서 얇은
|
||||||
|
프록시로 얻는 것) 아이디어까지 나왔지만 modifier/Ref의 컴포넌트 경계
|
||||||
|
통과 방식은 미해결(`research/component-composition-plan.md`, 위 "지금
|
||||||
|
할 일" 1번).
|
||||||
|
- **문서 전체 감사 및 정리** — `.claude/` 코퍼스 전체(약 15개 문서)를
|
||||||
|
서브에이전트로 감사해 여러 라운드에 걸쳐 쌓인 모순/중복/stale 마커를
|
||||||
|
대거 발견하고 수정(예: 이벤트 dot-access 확정 여부가 문서 내에서 서로
|
||||||
|
모순, 이미 해소된 질문이 "미해결"로 방치, 존재하지 않는 문서/섹션을
|
||||||
|
가리키는 끊긴 참조 다수, `TagService`/`CollectionService` 혼용 등).
|
||||||
|
`research/purity-and-effects-plan.md`도 내용이 이미 확정 상태라 `base/`로
|
||||||
|
승격. **이 CLAUDE.md 자체도 이번에 오래된 라운드별 인수인계 메모 3개를
|
||||||
|
이 요약 하나로 통합하며 정리함** — 라운드별 상세 히스토리가 필요하면
|
||||||
|
git log와 각 `base/`/`research/` 문서 안의 라운드 표시(예: "2026-08-04
|
||||||
|
3차 라운드")를 참고할 것, 여기서 전부 반복하지 않음.
|
||||||
|
|
||||||
2026-08-03에 확정됐다고 표시된 결정 전체(architecture.md 14개 + lifecycle-
|
용어 정리 제안 진행 중인 점은 위 "지금 할 일" 3번 참고.
|
||||||
pattern/store-semantics/bind-system-plan/module-lifecycle-plan/slot-plan/
|
|
||||||
tween-plan)를 `AskUserQuestion`으로 하나씩 예/아니오 검증 완료 — 상세는
|
|
||||||
`.claude/question.md`의 "2026-08-04 검증 라운드 완료" 절. 검증 과정에서
|
|
||||||
사용자가 실시간으로 설계를 더 전개하면서 **"State 프리미티브는 안 만든다"는
|
|
||||||
기존 결정이 틀렸다는 게 밝혀짐** — Store/State/Source 온톨로지 전체가 이
|
|
||||||
세션에서 새로 열린 가장 중요한 설계 스레드로 떠올랐음.
|
|
||||||
|
|
||||||
**같은 날 이어진 2차/3차 라운드에서 그 온톨로지와 인체공학 질문 전부를
|
|
||||||
확정함**: push-invalidate/pull-recompute 전파 모델(Fusion식 eager 노드/생성순
|
|
||||||
정렬 불필요), `:Compute`의 self/with 인자를 둘 다 lazy State 핸들로 통일
|
|
||||||
(별도 `ComputeWithout` 불필요), State는 쓰기 불가(값 쓰기는 Store의
|
|
||||||
`__newindex`로만) 확정, `Source`는 Store 내부 디테일이 아니라 값 하나만
|
|
||||||
다룰 때 쓰는 독립 공개 프리미티브로 격상, Slot 생존 확인 문제는 새 메커니즘
|
|
||||||
없이 기존 canExecute 유틸 재사용으로 해소(부수 효과로 "Store가 Store를 담을
|
|
||||||
때 이중 해제 방지 필요한가" 백로그 항목도 "명시적 dispose가 없어 질문 자체가
|
|
||||||
성립 안 함"으로 닫힘), `store.key` dot-access를 타입 추론 1급 경로로 삼는
|
|
||||||
관습을 인스턴스 생성까지 프로젝트 전역으로 확정, `RobloxFactory` 재호출
|
|
||||||
가드(같은 팩토리=무시, 다른 팩토리=에러, `New()`와는 인스턴스별 테이블
|
|
||||||
분리로 자연히 공존)까지 확정.
|
|
||||||
|
|
||||||
**4차 라운드에서 사용자가 실제 참고 코드(`.claude/initreq/artworks/`, PA님
|
|
||||||
작성 — UI 포함 전반적 설계 패턴을 시범 적용한 데모 모듈)를 공유해줘서
|
|
||||||
교차검증**: "DI"는 Dependency Injection이 아니라 Declarative Instance였음
|
|
||||||
(정정). 인스턴스 생성은 2트랙 구상보다 단순한 "제네릭 생성자 함수 하나 +
|
|
||||||
자주 쓰는 클래스만 정적 필드로 미리 바인딩" 모양으로 정정. **이벤트
|
|
||||||
바인딩은 `On.EventName` 도트액세스를 접고 PA님 방식(평범한 문자열 키 +
|
|
||||||
`ReflectionService` 기반 자동 판별)으로 전환** — Store의 dot-access는 실질적
|
|
||||||
타입 이득이 있어 그대로 유지, 이벤트만 예외. 전파 모델(push-invalidate/
|
|
||||||
pull-recompute)과 라이프사이클(GC-native)은 PA님 코드가 반례처럼 보였으나
|
|
||||||
(각각 파생 개념이 없는 단순 pub-sub, 전부 수동 해제) 재검토 후 **기존
|
|
||||||
확정 유지** — 라이프사이클은 나중에 하이브리드로 확장 가능한 여지만 기록.
|
|
||||||
OOP 회피 결정은 PA님의 `class.luau`도 같은 체이닝 상속 문제를 보여 오히려
|
|
||||||
보강됨. **더 이상 열려있는 핵심 설계 질문은 없음** — 남은 건 API 표면 이름뿐
|
|
||||||
(위 "지금 할 일" 참고). 그 외 자잘한 정정들(Slot retract=폐기 확정, Pipe COW
|
|
||||||
후보 폐기 등)은 각 문서에 바로 반영해둠 — 재조사 불필요.
|
|
||||||
|
|
||||||
이전 세션(2026-08-03) 종료 시점 메모: `.claude/` 전체 스캐폴드 + 대부분의
|
|
||||||
핵심 아키텍처 결정을 완료, 로컬 git 저장소 초기화+첫 커밋(원격 없음,
|
|
||||||
`SAFETY.md` 참고 — 원격은 사용자가 제한 계정을 마련해줘야 추가 가능).
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue