docs: .claude/ 코퍼스 정리 — reference/ 신설, 승격/기각 분리, 역전 이력 트리밍
base 밖으로 늘 읽을 필요 없는 참고자료(quad-v1-architecture, comparison-fusion-vide)를 새 reference/ 폴더로 분리하고, ui-shorthand-plan을 base로 승격(RoundSize 드롭+ UICorner/UIPadding/UIScale 리네임), additional-primitives-plan을 Blocker/Effect(base 승격)·Batch/Context(archive 기각)·키 기반 컬렉션 재조정(research 잔류)으로 4분할했다. component-composition-plan의 중복 역전 서사는 기존 archive 포인터로 압축하고, archive 제목 컨벤션을 [역전됨]/[기각됨]로 분화했다. tween-plan에는 retract/canExecute 구분 메모와 트윈 옵션 값 모양 논의를 추가했다. Effect가 Observer 변형인지는 임의로 결론내지 않고 question.md에 열린 질문으로 남겼다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
e4d6181fcf
commit
5c9d10df66
26 changed files with 731 additions and 476 deletions
|
|
@ -9,10 +9,11 @@
|
||||||
|
|
||||||
| 폴더 | 기준 |
|
| 폴더 | 기준 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `base/` | 결정 완료 + 프로젝트 전체에 걸치는 컨텍스트 — plan/done 개념 없음, 계속 참조되는 배경지식 |
|
| `base/` | 결정 완료 + 프로젝트 전체에 걸치는 컨텍스트 — plan/done 개념 없음, 계속 참조되는 배경지식. **항상 읽어야 하는** 배경지식만 여기 둠(다른 문서를 이해하는 데 전제되는 것) |
|
||||||
|
| `reference/` | **[2026-08-07 신설]** 결정 자체가 아니라 다른 문서가 근거로 인용하는 온디맨드 참고 자료(v1 스냅샷, 프레임워크 비교 리서치) — "완료" 개념 없는 건 `base/`와 같지만, 항상 읽을 필요는 없고 해당 문서가 인용될 때만 열어보면 됨. `quadnomicon` 소재 후보가 많음 |
|
||||||
| `research/` | 아직 착수 전, 사용자와 스코프/설계를 더 상의해야 함 |
|
| `research/` | 아직 착수 전, 사용자와 스코프/설계를 더 상의해야 함 |
|
||||||
| `qa-request/` | 구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음 — 지금은 구현 자체가 시작 전이라 비어있음 |
|
| `qa-request/` | 구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음 — 지금은 구현 자체가 시작 전이라 비어있음 |
|
||||||
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). **[2026-08-06 확장]** 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용 — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 `base/`/`research/`에서 뺌) `quadnomicon` 소재로는 나중에 쓸 수 있음 |
|
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). **[2026-08-06 확장]** 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용(제목 `[역전됨]` — 한 번 확정했다가 뒤집힌 것) — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 `base/`/`research/`에서 뺌) `quadnomicon` 소재로는 나중에 쓸 수 있음. **[2026-08-07 확장]** 후보였다가 채택 안 된 것(확정한 적 없이 검토 후 기각)도 같은 방식으로 보존, 제목은 구분을 위해 `[기각됨]` — `[역전됨]`과 의미가 다르므로 혼동하지 말 것 |
|
||||||
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — 지금은 비어있음(구현 시작 전) |
|
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — 지금은 비어있음(구현 시작 전) |
|
||||||
| `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`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
|
| `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`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
|
||||||
|
|
||||||
|
|
@ -25,8 +26,6 @@
|
||||||
| 문서 | 내용 |
|
| 문서 | 내용 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `architecture.md` | quad-v2 전체 아키텍처 확정 사항 요약(제일 먼저 볼 문서) |
|
| `architecture.md` | quad-v2 전체 아키텍처 확정 사항 요약(제일 먼저 볼 문서) |
|
||||||
| `quad-v1-architecture.md` | v1(`initreq/quad`) 내부 동작 스냅샷 — "이 문제를 안 반복하려면"의 기준선 |
|
|
||||||
| `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차 라운드) |
|
||||||
|
|
@ -34,20 +33,28 @@
|
||||||
| `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정 |
|
| `slot-plan.md` | 뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정 |
|
||||||
| `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정, getter 이름만 남음 |
|
| `modifier-plan.md` | Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정, getter 이름만 남음 |
|
||||||
| `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 |
|
| `purity-and-effects-plan.md` | 컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 |
|
||||||
| `component-composition-plan.md` | 컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, Source가 State를 구조적으로 만족(`StoreSource`/`RefSource` 중간안은 전부 폐기됨) — modifier/Ref 컴포넌트 경계 통과까지 전부 확정, 남은 건 API 이름뿐 [정정: 2026-08-04 승격됐으나 이 표에 반영이 안 돼있던 걸 2026-08-06 뒤늦게 수정] |
|
| `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번) |
|
||||||
|
| `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 신설)
|
||||||
|
|
||||||
|
| 문서 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| `quad-v1-architecture.md` | v1(`initreq/quad`) 내부 동작 스냅샷 — "이 문제를 안 반복하려면"의 기준선. **[2026-08-07 `base/`→`reference/` 이동]** v2의 결정 자체가 아니라 다른 문서가 인용하는 온디맨드 자료라 항상 읽을 필요는 없음 |
|
||||||
|
| `comparison-fusion-vide.md` | Fusion/Vide 아키텍처 비교 리서치 — 설계 결정 근거 자료(전파 모델 등 일부 서술은 이후 라운드에서 뒤집혔으니 `bind-system-plan.md` 쪽을 최신으로 볼 것). **[2026-08-07 `base/`→`reference/` 이동]**, `quadnomicon` 소재 후보 |
|
||||||
|
|
||||||
## `research/` — 아직 착수 전, 상의 필요
|
## `research/` — 아직 착수 전, 상의 필요
|
||||||
|
|
||||||
| 문서 | 내용 | 우선순위 |
|
| 문서 | 내용 | 우선순위 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `tween-plan.md` | 트윈을 Store 밖 특수 bind key로 처리, 기본 오버라이드는 Cancel | 중 — 세부 옵션만 남음 |
|
| `tween-plan.md` | 트윈을 Store 밖 특수 bind key로 처리, 기본 오버라이드는 Cancel, 트윈 옵션 값 모양(TweenInfo vs 편의 필드)은 신규 열린 논의 | 중 — 세부 옵션만 남음 |
|
||||||
| `existing-instance-bind-plan.md` | 이미 생성된 인스턴스 재바인드 — 착수 안 하되 "미지원" 확정도 안 함, 열린 가능성 유지 | 하 — v2 초기 스코프 제외 |
|
| `existing-instance-bind-plan.md` | 이미 생성된 인스턴스 재바인드 — 착수 안 하되 "미지원" 확정도 안 함, 열린 가능성 유지 | 하 — v2 초기 스코프 제외 |
|
||||||
| `debug-tooling-plan.md` | 실물 Instance→코드 위치 역추적 Studio 플러그인(`quad-debug`) — 채널 실현 가능성(BindableEvent/Function 크로스 컨텍스트)까지 실측 검증 완료, 세부 API 이름·구현만 남음 | 하 — 사용자가 "quad 개발 완료 전엔 착수 못 함"으로 직접 후순위 지정, base 설계 시 훅 확장 지점만 고려 |
|
| `debug-tooling-plan.md` | 실물 Instance→코드 위치 역추적 Studio 플러그인(`quad-debug`) — 채널 실현 가능성(BindableEvent/Function 크로스 컨텍스트)까지 실측 검증 완료, 세부 API 이름·구현만 남음 | 하 — 사용자가 "quad 개발 완료 전엔 착수 못 함"으로 직접 후순위 지정, base 설계 시 훅 확장 지점만 고려 |
|
||||||
| `documentation-plan.md` | 문서 사이트 구조(초심자/api/심화/`quadnomicon` 4축, 백엔드별 트랙 분리) + UI 네이밍 컨벤션·Store 부작용 패턴·권장 이벤트 핸들링 3개 세부 문서 뼈대 | 하 — 착수 시점 미정, 구조/스코프만 합의된 상태 |
|
| `documentation-plan.md` | 문서 사이트 구조(초심자/api/심화/`quadnomicon` 4축, 백엔드별 트랙 분리) + UI 네이밍 컨벤션·Store 부작용 패턴·권장 이벤트 핸들링 3개 세부 문서 뼈대 | 하 — 착수 시점 미정, 구조/스코프만 합의된 상태 |
|
||||||
| `documentation-content-map.md` | 위 4축에 실제로 뭘 채울지 `base/` 전체를 초심자/api/심화/skip으로 서베이한 콘텐츠 맵 — 초심자 core loop 목차 초안 포함 | 하 — 문서화 착수 시점의 목차/우선순위표로 쓸 것 |
|
| `documentation-content-map.md` | 위 4축에 실제로 뭘 채울지 `base/` 전체를 초심자/api/심화/skip으로 서베이한 콘텐츠 맵 — 초심자 core loop 목차 초안 포함 | 하 — 문서화 착수 시점의 목차/우선순위표로 쓸 것 |
|
||||||
| `framework-comparison-findings.md` | quad vs Fusion/Vide/react-lua 정직한 비교(실 소스 근거) — quad 강점, 진짜 불리한 점 중 고칠 만한 것 3개, 못 고치는 트레이드오프 정리 | 하 — 사용자 검토 후 반영 여부 결정 대기 |
|
| `framework-comparison-findings.md` | quad vs Fusion/Vide/react-lua 정직한 비교(실 소스 근거) — quad 강점, 진짜 불리한 점 중 고칠 만한 것 3개, 못 고치는 트레이드오프 정리 | 하 — 사용자 검토 후 반영 여부 결정 대기 |
|
||||||
| `ui-shorthand-plan.md` | UICorner/UIPadding/UIScale 인라인 편의 키(v1 `Corner`/`PaddingAll`/`Scale` 선례) — 여전히 필요한 기능으로 재확정(RoundSize만 네이티브 UICorner로 대체돼 불필요), 메커니즘(Handler)·패키지 배치(quad-roblox 코어) 확정 | 하 — 결론 남, M10 전후 구현하면 됨 |
|
| `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` | 확정 프리미티브(Source/State/Store/Ref/Observer/Modifier/Slot/DI)만으로 충분한지 웹 프레임워크·Fusion/Vide/v1 소스 근거로 조사 — 키 기반 동적 컬렉션 재조정(Fusion `ForPairs`/Vide `indexes()`류)이 가장 명확한 빈 자리로 확인, Effect/Batch/Context는 부차적 후보 | 상 — 사용자 판단 대기, 착수 전 M0/M1 스코프에 영향 줄 수 있음 |
|
|
||||||
| `pre-implementation-audit.md` | M0 착수 직전 크리티컬 감사(2026-08-06 신설) — `base/` 전체를 모호성/지연결정리스크/단순화후보 세 렌즈로 재검토, 11개 우선순위1(M0~M4 착수 전 확인 권장) + 11개 우선순위2 + 2개 단순화후보 | 상 — M0 착수 전 최소 우선순위1 항목 확인 권장 |
|
| `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 코어 구현 시점까지 미결 |
|
| `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 코어 구현 시점까지 미결 |
|
||||||
|
|
||||||
|
|
@ -55,7 +62,11 @@
|
||||||
|
|
||||||
| 문서 | 내용 |
|
| 문서 | 내용 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `store-source-proxy-reversed.md` | 2026-08-04에 확정했던 `StoreSource` 프록시 설계(Store가 Source를 감춘 별도 프록시로 감쌈) — 2026-08-06 세 번째 세션에서 "Source가 State를 구조적으로 만족" 재구성으로 완전히 대체됨. 원문·역전 이유·신구 비교표 보존, `quadnomicon` 소재 후보 |
|
| `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`)로 대체 |
|
||||||
|
| `context-rejected.md` | **[기각됨, 2026-08-07 신설]** `Context`(트리 하위 암묵 전파) + 대안이던 레이어드 Store 둘 다 기각 — 명시적 타입 강제 Store 전달로 충분하다는 판단 |
|
||||||
|
|
||||||
## 참고
|
## 참고
|
||||||
|
|
||||||
|
|
|
||||||
63
.claude/archive/batch-rejected.md
Normal file
63
.claude/archive/batch-rejected.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# [기각됨] `Batch(fn)` — lexical block 기반 지연/합치기, `Blocker`로 대체됨
|
||||||
|
|
||||||
|
**기각 일시**: 2026-08-06~07. **현재 유효한 설계**: `base/
|
||||||
|
additional-primitives.md`의 "Blocker" 절 — 이 문서가 다루는 것과 같은
|
||||||
|
문제("여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게")를
|
||||||
|
값 기반으로 풀어 대체함. 이 파일은 더 이상 능동적으로 참고할 필요 없음
|
||||||
|
(구현에 안 씀) — "왜 lexical Batch를 기각하고 값 기반 Blocker를 택했는가"가
|
||||||
|
`quadnomicon`(프레임워크 설계자용 심화 콘텐츠) 소재로 가치 있어서 사유를
|
||||||
|
통째로 보존해둔 것.
|
||||||
|
|
||||||
|
**중요**: 이건 "값 기반 지연/합치기"라는 문제 자체를 기각한 게 아니라,
|
||||||
|
**그 문제를 lexical block(Solid `batch()`/MobX `runInAction()`류)으로
|
||||||
|
풀려는 접근만** 기각한 것 — 실제 해법은 완전히 다른 별개 primitive인
|
||||||
|
`Blocker`로 채택됨.
|
||||||
|
|
||||||
|
## 무엇을 검토했었나
|
||||||
|
|
||||||
|
`Batch(fn)`을 "플래그 세우고 `fn` 실행, 끝나면 flush"로 구현하는 안 —
|
||||||
|
`fn` 안에서 여러 `Set()`을 몰아서 호출해도 소비자에게 전파는 `fn`이 끝난
|
||||||
|
뒤 딱 한 번만 되게 하는, 함수/코루틴 스코프 lexical transaction 블록.
|
||||||
|
|
||||||
|
### "즉시 pull"이 뭔지 (참고용 예시)
|
||||||
|
|
||||||
|
store-bind 핸들러(예: `Frame { BackgroundColor3 = total }`)는 lazy가
|
||||||
|
아니다 — 화면에 실제로 반영하는 "누군가"가 바로 이 핸들러 자신이라,
|
||||||
|
무효화 신호를 받자마자 스스로 `Get()`하고 바로 대입한다:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local total = a:With(b):Compute(function(av, bv) return av + bv end)
|
||||||
|
Frame { BackgroundColor3 = total }
|
||||||
|
|
||||||
|
a:Set(1) -- total 무효화 → 핸들러가 즉시 (av=1, bv=이전b)로 재계산+대입
|
||||||
|
b:Set(2) -- total 무효화 → 핸들러가 즉시 (av=1, bv=2)로 다시 재계산+대입
|
||||||
|
-- BackgroundColor3가 중간값을 한 번 거쳐가고, 두 번 대입됨
|
||||||
|
```
|
||||||
|
|
||||||
|
## 기각 이유 — 코루틴 yield 위에서 구조적으로 위험
|
||||||
|
|
||||||
|
`Batch(fn)`을 "플래그 세우고 `fn` 실행, 끝나면 flush"로 구현하면 **`fn`이
|
||||||
|
yield하는 순간 위험해진다**(사용자 지적, 정확함):
|
||||||
|
|
||||||
|
1. 플래그가 전역이면, yield 중 스케줄러가 돌리는 무관한 코루틴의 `Set()`이
|
||||||
|
이 Batch에 잘못 휘말릴 수 있음.
|
||||||
|
2. 플래그를 코루틴 스코프로 만들어도(Fusion `Contextual`류 코루틴 키 weak
|
||||||
|
table), `fn` 안에서 새 코루틴을 스폰하는 API(Promise, `task.spawn`)를
|
||||||
|
부르면 그 새 코루틴은 배치 스코프를 상속 못 받아 일부 Set이 새어나감.
|
||||||
|
3. `fn`이 영원히 재개 안 되면(장시간 대기, 리크된 코루틴) flush가 영영 안
|
||||||
|
일어나 store-bind 핸들러들이 화면을 무기한 stale 상태로 방치 — 즉시
|
||||||
|
pull보다 더 나쁜 실패 모드.
|
||||||
|
|
||||||
|
이건 구현을 잘 짜서 피할 수 있는 버그가 아니라 **lexical block 모델
|
||||||
|
자체가 협조적 스케줄링 환경과 구조적으로 안 맞는 것**으로 판단, 기각.
|
||||||
|
|
||||||
|
## 왜 완전히 헛수고는 아니었나
|
||||||
|
|
||||||
|
"지연 구간을 표현하고 싶다"는 문제의식 자체는 정확했고, `Blocker`가
|
||||||
|
정확히 그 문제를 콜스택/코루틴이 아니라 **값**(`Blocker` 객체의
|
||||||
|
`On()`/`Off()`)으로 표현해 풀었다 — Batch가 무너뜨렸던 세 가지 실패
|
||||||
|
모드(전역 플래그 오염, 새 코루틴 미상속, 영구 yield로 인한 무기한 방치)가
|
||||||
|
Blocker에선 구조적으로 전부 해당 안 됨(`On()`/`Off()` 사이에 얼마나 많은
|
||||||
|
yield/코루틴 전환이 끼어도, 심지어 완전히 다른 코루틴에서 `Off()`를
|
||||||
|
불러도 문제없음). `quadnomicon`에서 "콜스택/코루틴 스코프로 상태를
|
||||||
|
표현하려던 시도가 왜 항상 위험한가"의 구체 사례로 쓰기 좋음.
|
||||||
61
.claude/archive/context-rejected.md
Normal file
61
.claude/archive/context-rejected.md
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# [기각됨] `Context`(트리 하위 암묵 전파) + 대안이었던 "레이어드 Store"
|
||||||
|
|
||||||
|
**기각 일시**: 2026-08-06~07. **현재 유효한 설계**: 명시적 타입 강제
|
||||||
|
Store 전달(`props.Theme: Store<Theme>`처럼 컴포넌트가 필요한 걸 named
|
||||||
|
parameter로 명시적으로 요구) + 오버라이드가 필요한 지점에서
|
||||||
|
`Store({...부모값, 변경필드=새값})`을 한 번 명시적으로 만들어 그 지점부터
|
||||||
|
평소처럼 prop으로 넘기는 것 — 새 primitive 없이 이미 있는 Modifier의
|
||||||
|
"merge, 나중 게 이김" 패턴 재사용. 이 파일은 더 이상 능동적으로 참고할
|
||||||
|
필요 없음(구현에 안 씀) — "왜 Context가 없는가"가 `quadnomicon`(프레임워크
|
||||||
|
설계자용 심화 콘텐츠) 소재로 가치 있어서 사유를 통째로 보존해둔 것.
|
||||||
|
|
||||||
|
## 무엇을 검토했었나
|
||||||
|
|
||||||
|
React `Context`/Vue `provide`-`inject`류, 트리 상위에서 값을 하나 심어두면
|
||||||
|
중간 컴포넌트가 명시적으로 전달하지 않아도 하위 어디서든 그 값을 읽을 수
|
||||||
|
있는 암묵적 전파 메커니즘.
|
||||||
|
|
||||||
|
### 난이도 판정 요약
|
||||||
|
|
||||||
|
서브에이전트 조사 결과: 동기적 저작 트리에 한정한 얕은 버전(Fusion
|
||||||
|
`Contextual`류 코루틴 키 weak table push-pop)은 구현 난이도 **낮음**이지만,
|
||||||
|
quad가 정상 패턴으로 확정한 "Slot에 이벤트 핸들러/코루틴에서 비동기로
|
||||||
|
자식이 추가되는 경우"엔 **에러 없이 조용히 기본값으로 폴백**하는 함정이
|
||||||
|
있음. 완전 자동(비동기 추가까지 자동 전파)은 Roblox Luau에
|
||||||
|
thread-local/async-context-propagation 훅이 없어 **사실상 불가**(플랫폼
|
||||||
|
한계, Node `AsyncLocalStorage`/Python `contextvars`도 "자동"이 아니라
|
||||||
|
"async 경계마다 명시적 캡처+재진입"으로 같은 문제를 품).
|
||||||
|
|
||||||
|
## 기각 이유
|
||||||
|
|
||||||
|
얕은 버전조차: (1) `base/slot-plan.md`가 정상 패턴으로 명시한 Slot 비동기
|
||||||
|
추가에서 가장 먼저, 가장 조용히 깨짐. (2) `research/debug-tooling-plan.md`의
|
||||||
|
"모든 연결은 선언된 그래프여야 한다"는 quad-debug 철학과 충돌하는 안 보이는
|
||||||
|
채널을 만듦.
|
||||||
|
|
||||||
|
## 대안이었던 "레이어드 Store"도 철회 (사용자 반박 수용)
|
||||||
|
|
||||||
|
Context 대신 권고했던 대안 — "레이어드 Store"(자식 Source 모음이 없는
|
||||||
|
키는 부모로 `__index` 폴백)도 사용자 반박으로 철회됨:
|
||||||
|
|
||||||
|
- quad는 이미 **타입으로 강제되는 명시적 Store 전달**을 갖고 있고, 이건
|
||||||
|
Context의 "Provider 안 넣으면 조용히 기본값/에러"보다 **더 안전**하다
|
||||||
|
(컴파일타임에 걸림 vs 런타임에 조용히 새는 값) — Context보다 나은 지점.
|
||||||
|
- "필드 일부만 오버라이드, 나머지는 부모 값 그대로"가 필요하면, 오버라이드
|
||||||
|
지점에서 `Store({...부모값, 변경필드=새값})`을 **한 번 명시적으로**
|
||||||
|
만들어서 그 지점부터 평소처럼 prop으로 넘기면 끝 — Modifier가 이미 쓰는
|
||||||
|
"merge, 나중 게 이김" 패턴 재사용 가능, 새 primitive 불필요. 레이어드
|
||||||
|
Store(읽는 시점에 몇 단계를 거슬러 올라가는지 안 보이는 자동 폴백)는
|
||||||
|
정확히 Context와 같은 이유(디버깅 어려움 — "이 값이 왜 이거지?"를
|
||||||
|
추적하려면 부모 체인을 다 훑어야 함)로 얻는 것보다 잃는 게 크다.
|
||||||
|
- 서드파티 라이브러리가 뭔가 필요하면, 타입이 강제하는 명시적 요구
|
||||||
|
(`props.Theme: Store<Theme>`)가 "몰래 안 줘서 죽는다"보다 나은 실패
|
||||||
|
모드 — Slot 기반 저작 모델(부모가 자손을 직접 구성)에서도 이런 요청은
|
||||||
|
자연스럽게 props로 흐른다.
|
||||||
|
|
||||||
|
## 결론
|
||||||
|
|
||||||
|
Context, 레이어드 Store 둘 다 프리미티브로 만들지 않음. "왜 Context가
|
||||||
|
없는가"(명시적 Store 전달이 이미 그 역할을 하고, 타입 강제가 Context의
|
||||||
|
실패 모드보다 안전하다는 논증)는 `quadnomicon` 에세이 후보로 등록
|
||||||
|
(`research/documentation-content-map.md` 참고).
|
||||||
44
.claude/archive/ui-shorthand-roundsize-dropped.md
Normal file
44
.claude/archive/ui-shorthand-roundsize-dropped.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# [기각됨] `RoundSize`(이미지 9-slice 라운드 트릭) 포팅 — 네이티브 `UICorner`로 대체되어 불필요
|
||||||
|
|
||||||
|
**기각 일시**: 2026-08-06. **현재 유효한 설계**: `base/ui-shorthand-plan.md` —
|
||||||
|
이 문서는 v1의 `RoundSize`가 왜 포팅 대상에서 빠졌는지, 그리고 그 판단이
|
||||||
|
한 차례 잘못 일반화됐다가 정정된 이력을 보존해둔 것. 능동적으로 참고할
|
||||||
|
필요 없음(구현에 안 씀) — `RoundSize`류 "네이티브 Instance가 나중에 생겨
|
||||||
|
워크어라운드가 필요 없어진 사례"는 `quadnomicon` 소재로 가치 있음.
|
||||||
|
|
||||||
|
## 무엇이었나
|
||||||
|
|
||||||
|
v1 `class.lua`가 지원하던 특수 키 `RoundSize = 16`(`ImageLabel`/
|
||||||
|
`ImageButton` 전용) — `UICorner`가 아니라 이미지 자체를 9-slice로 잘라
|
||||||
|
둥글게 보이게 만드는 트릭(`round.SetRound()`). `UICorner`/`UIPadding`/
|
||||||
|
`UIScale` 자동 생성 숏핸드(`Corner`/`PaddingAll`/`Scale`, 현재
|
||||||
|
`base/ui-shorthand-plan.md`가 이어받은 기능)와 겉보기엔 "인라인 리터럴 값
|
||||||
|
하나로 GUI를 꾸민다"는 카테고리가 비슷해 보이지만, **메커니즘 자체가
|
||||||
|
완전히 다름**(하나는 별도 Instance 생성, 하나는 이미지 처리) — 이 문서가
|
||||||
|
쓰인 이유가 바로 이 둘을 혼동하지 않기 위함.
|
||||||
|
|
||||||
|
## 기각 이유
|
||||||
|
|
||||||
|
`RoundSize`는 **당시 Roblox에 `UICorner` 같은 네이티브 구현체가 없었기
|
||||||
|
때문에** 존재하던 워크어라운드였음. 지금은 `UICorner`가 안정적인 네이티브
|
||||||
|
Instance라 이미지 대상에도 그냥 실제 `UICorner`를 붙이면 되므로, 이미지를
|
||||||
|
9-slice로 잘라 둥글게 "보이게" 만드는 트릭 자체를 그대로 포팅할 이유가
|
||||||
|
없음 — **포팅 안 함으로 확정**.
|
||||||
|
|
||||||
|
## 왜 archive에 남기나 — 한 차례 과잉일반화됐다가 정정된 이력
|
||||||
|
|
||||||
|
`RoundSize` 하나를 드롭하기로 한 판단이, 초안 작성 과정에서 실수로
|
||||||
|
**"UICorner가 네이티브가 됐으니 Corner/PaddingAll/Scale 숏핸드 자체가
|
||||||
|
불필요하다"는 훨씬 넓은 결론으로 잘못 일반화된 적이 있었음**("이전 정리
|
||||||
|
('포팅 불필요')는 오해였고 정정함"). 사용자가 직접 반박해 정정됨:
|
||||||
|
`UICorner`가 네이티브 Instance가 됐다는 사실은 "이미지를 트릭으로 둥글게
|
||||||
|
보이게 할 필요가 없어졌다"는 것만 의미할 뿐 — `UIScale`/`UIPadding`류가
|
||||||
|
**여전히 부모에 Parent해야 하는 별도 Instance**라는 구조적 사실 자체는
|
||||||
|
전혀 안 바뀌었으므로, `Corner`/`PaddingAll`/`Scale` 숏핸드(현재
|
||||||
|
`UICorner`/`UIPadding`/`UIScale`)의 존재 이유는 그대로 유효.
|
||||||
|
|
||||||
|
**교훈(재사용 가능)**: "네이티브 Instance가 생겼다"는 사실 하나로부터
|
||||||
|
"관련 숏핸드 전체가 불필요해졌다"를 성급히 일반화하지 말 것 — 워크어라운드가
|
||||||
|
드롭되는 이유(네이티브 대체재 등장)와 편의 숏핸드가 필요한 이유(별도
|
||||||
|
Instance를 만들어 Parent해야 하는 구조적 번거로움)는 서로 다른 축이라,
|
||||||
|
하나가 해소됐다고 다른 하나도 자동으로 해소되는 게 아님.
|
||||||
157
.claude/base/additional-primitives.md
Normal file
157
.claude/base/additional-primitives.md
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
# 추가 확정 프리미티브 — Blocker / Effect
|
||||||
|
|
||||||
|
**상태**: 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를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게
|
||||||
|
|
||||||
|
**왜 필요한가**: `state1, state2 -> state3`처럼 여러 소스가 한 파생값에
|
||||||
|
합류할 때, 둘을 한 번에 바꾸면 소비자에게 두 번 전파(재계산+재대입)되는
|
||||||
|
문제. lexical `Batch(fn)`(Solid `batch()`/MobX `runInAction()`류)으로
|
||||||
|
풀려던 접근은 코루틴 yield 위에서 구조적으로 위험해 기각됨 — 상세 근거는
|
||||||
|
`archive/batch-rejected.md` 참고, 여기서 반복하지 않음. **Blocker는 그
|
||||||
|
문제를 콜스택/코루틴이 아니라 사용자가 들고 있는 "값"으로 표현**해서 이
|
||||||
|
위험을 구조적으로 우회한다.
|
||||||
|
|
||||||
|
**store 개발(M3)과 밀접하게 연관됨** — `state:Block(blocker)`가 State
|
||||||
|
위에 얹히는 메소드이므로 `base/store-semantics.md`의 Store/State/Source
|
||||||
|
온톨로지, 특히 push-invalidate/pull-recompute 전파 모델(`base/
|
||||||
|
bind-system-plan.md` "전파 모델 확정" 절)을 전제로 함. 별도 파일로 두되
|
||||||
|
State와 같은 마일스톤(`ROADMAP.md` M3)에서 함께 구현할 것.
|
||||||
|
|
||||||
|
### 메커니즘 (확정)
|
||||||
|
|
||||||
|
```
|
||||||
|
Blocker() -> blocker -- 생성자
|
||||||
|
blocker:On() -> self -- IsBlocked = true로만 설정, 그 외 아무것도 안 함
|
||||||
|
blocker:Off() -> self -- IsBlocked = false로 먼저 설정, 그 다음 등록된
|
||||||
|
-- onunblock 핸들 전부 실행(순서 무관, idempotent)
|
||||||
|
|
||||||
|
state:Block(blocker) -> state -- 새 gated state 반환. **호출되는 즉시**(나중에
|
||||||
|
-- 처음 블록될 때가 아니라) onunblock 핸들을
|
||||||
|
-- blocker의 weak 배열에 등록.
|
||||||
|
```
|
||||||
|
|
||||||
|
gated state의 동작:
|
||||||
|
- 원본 state가 emit(무효화)될 때, 이 gated state로 전파를 시도.
|
||||||
|
- `blocker.IsBlocked`이면: 전파 안 하고 `HasBlockedEmit = true`만 세팅.
|
||||||
|
- `blocker.IsBlocked`가 아니면: 평소처럼 그냥 전파(투명하게 통과).
|
||||||
|
- `blocker:Off()`가 실행하는 onunblock 핸들은: `HasBlockedEmit`을 확인해
|
||||||
|
true면 그제서야 정확히 1회 전파(emit)하고 플래그를 리셋. 이미 false면
|
||||||
|
아무 것도 안 함(idempotent).
|
||||||
|
|
||||||
|
**`:Get()`엔 영향 없음** — 블록은 emit **전파**만 지연시킨다. 블록 중이라도
|
||||||
|
누군가 명시적으로 `:Get()`하면 그 순간의 실제 값을 정상적으로 계산해서
|
||||||
|
준다 — `store-semantics.md`의 "`Get()`은 라이브 레퍼런스를 준다" 원칙과 일치.
|
||||||
|
|
||||||
|
### 사용 예시
|
||||||
|
|
||||||
|
`state1`/`state2` 각각이 아니라 **결합된 결과(`state3`) 하나에만** `:Block`을
|
||||||
|
건다:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local blocker = Blocker()
|
||||||
|
local gated3 = state3:Block(blocker) -- 소비자는 gated3를 구독
|
||||||
|
|
||||||
|
blocker:On()
|
||||||
|
state1:Set(1) -- state3 무효화 → gated3로 전파 시도 → 블록됨 → HasBlockedEmit=true
|
||||||
|
state2:Set(2) -- state3 무효화 → gated3로 전파 시도 → 이미 true, 그대로
|
||||||
|
blocker:Off() -- onunblock 핸들 실행 → HasBlockedEmit 확인 → 딱 한 번 emit
|
||||||
|
```
|
||||||
|
|
||||||
|
**일반 사용 가이드(확정, 문서화 필수)**: Block은 **파이프라인의 최종 연산
|
||||||
|
지점**(실제로 무거운 계산이 일어나는 derived state, eager 소비자에 가장
|
||||||
|
가까운 지점)에 거는 게 원칙 — 소스가 여러 개든, 하나가 한 주기에 여러 번
|
||||||
|
바뀌든 상관없이 이 지점 하나만 지키면 됨. 소스 쪽에 각각 거는 게 아니다.
|
||||||
|
|
||||||
|
### 이름 확정
|
||||||
|
|
||||||
|
- 클래스: `Blocker` — `Observer`/`Modifier`/`Ref`와 같은 명사-행위자
|
||||||
|
네이밍 관례와 일치.
|
||||||
|
- Blocker 자신의 토글: **`On()`/`Off()` -> self** (`Block()`/`Unblock()`
|
||||||
|
아님) — `state:Block(blocker)`가 이미 "배선(wiring)" 동작의 동사로
|
||||||
|
"Block"을 쓰고 있어서, Blocker 자신의 토글까지 같은 단어를 쓰면
|
||||||
|
`blocker:Block()`(블로커를 켠다)과 `state:Block(blocker)`(state를 이
|
||||||
|
블로커에 배선한다)가 같은 단어로 다른 두 동작을 가리키게 됨.
|
||||||
|
- 필드: **`IsBlocked`**(Blocker 자신의 On/Off 상태), **`HasBlockedEmit`**
|
||||||
|
(gated state의 대기 플래그, `Is`/`Has` 접두어로 불리언임을 바로 알려줌).
|
||||||
|
- 메소드: `state:Block(blocker) -> state`.
|
||||||
|
|
||||||
|
### 재진입(네스팅) — 의도적으로 미지원, 강한 문서화 필수
|
||||||
|
|
||||||
|
`IsBlocked`는 카운터가 아니라 단순 불리언이고, **의도적으로 그렇게 둔다.**
|
||||||
|
레퍼런스 카운팅으로 네스팅을 지원할 수도 있었지만, 그러면 "`On()` 여러
|
||||||
|
번, `Off()` 실수로 적게" 같은 버그가 **영구 블록으로 조용히 새는** 더
|
||||||
|
위험한 실패 모드를 만든다("poisoned mutex" 트래킹류 해키함도 만들지
|
||||||
|
않기로 함).
|
||||||
|
|
||||||
|
**대신 확정된 규칙**: 겹치는 배치가 필요하면 **각자 새 `Blocker` 인스턴스를
|
||||||
|
만들 것** — 하나의 Blocker를 여러 컨텍스트에서 재사용/중첩하지 않는다.
|
||||||
|
`Off()`는 스태킹 없이 즉시 그 자리에서 꺼진다. **이 제약은 반드시 사용자
|
||||||
|
문서(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`에 같은 항목 등재됨.
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
**상태**: base — 횡단 결정의 최종 상태 요약. 특정 기능 plan이 아니라 프로젝트
|
**상태**: base — 횡단 결정의 최종 상태 요약. 특정 기능 plan이 아니라 프로젝트
|
||||||
전체에 걸친 결정이라 완료 개념 없음. 근거가 된 원본 브레인스토밍은
|
전체에 걸친 결정이라 완료 개념 없음. 근거가 된 원본 브레인스토밍은
|
||||||
`.claude/initreq/raw-userinput.md`(안 옮기고 그대로 둠 — 이 문서들로 나누기 전의
|
`.claude/initreq/raw-userinput.md`(안 옮기고 그대로 둠 — 이 문서들로 나누기 전의
|
||||||
raw chain-of-thought 백업 역할). 현재 v1 구조는 `base/quad-v1-architecture.md`,
|
raw chain-of-thought 백업 역할). 현재 v1 구조는 `reference/quad-v1-architecture.md`,
|
||||||
비교 리서치는 `base/comparison-fusion-vide.md`, `base/lifecycle-pattern.md` 참고.
|
비교 리서치는 `reference/comparison-fusion-vide.md`, `base/lifecycle-pattern.md` 참고.
|
||||||
|
|
||||||
## 한 줄 요약
|
## 한 줄 요약
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo
|
||||||
바인드처럼 정말 체인이 자연스러운 곳에만(`:` 문법) 남김. 타입 작성 난이도가
|
바인드처럼 정말 체인이 자연스러운 곳에만(`:` 문법) 남김. 타입 작성 난이도가
|
||||||
OOP 스타일에서 너무 커진다는 게 이유.
|
OOP 스타일에서 너무 커진다는 게 이유.
|
||||||
3. **복사(clone) 구현 지양, 팩토리 함수로 대체.** v1의 metatable 체이닝(1-필드
|
3. **복사(clone) 구현 지양, 팩토리 함수로 대체.** v1의 metatable 체이닝(1-필드
|
||||||
테이블을 계속 쌓는 방식, `base/quad-v1-architecture.md` 참고)은 폐기.
|
테이블을 계속 쌓는 방식, `reference/quad-v1-architecture.md` 참고)은 폐기.
|
||||||
store 바인드에 대한 변경은 "전체 변경"으로 간주(UB 아님, 문서화된 의미론) —
|
store 바인드에 대한 변경은 "전체 변경"으로 간주(UB 아님, 문서화된 의미론) —
|
||||||
부분 복사/오버레이가 필요하면 팩토리 함수로 필요한 곳만 명시적으로 복사.
|
부분 복사/오버레이가 필요하면 팩토리 함수로 필요한 곳만 명시적으로 복사.
|
||||||
4. **PA님 스타일 DI 키 계속 지원**: `[Attribute "Name"]`, `[Tag ""] = true` 같은
|
4. **PA님 스타일 DI 키 계속 지원**: `[Attribute "Name"]`, `[Tag ""] = true` 같은
|
||||||
|
|
@ -67,12 +67,12 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo
|
||||||
구현(`base/bind-system-plan.md`).
|
구현(`base/bind-system-plan.md`).
|
||||||
9. **Tracker 미구현.** v1의 소스 변경 감지 자동 재렌더 기능(hot-reload watcher,
|
9. **Tracker 미구현.** v1의 소스 변경 감지 자동 재렌더 기능(hot-reload watcher,
|
||||||
실제로는 `.claude/initreq/quad/src/tracker.lua` — v1에서도 이미 `exports.lua`에
|
실제로는 `.claude/initreq/quad/src/tracker.lua` — v1에서도 이미 `exports.lua`에
|
||||||
연결 안 된 죽은 코드였음, `base/quad-v1-architecture.md` 참고)은 렌더
|
연결 안 된 죽은 코드였음, `reference/quad-v1-architecture.md` 참고)은 렌더
|
||||||
라이브러리 범위 밖으로 판단. 스토리북 구현체(https://ui-labs.luau.page/docs/getstarted)가
|
라이브러리 범위 밖으로 판단. 스토리북 구현체(https://ui-labs.luau.page/docs/getstarted)가
|
||||||
이미 존재하므로 대체.
|
이미 존재하므로 대체.
|
||||||
10. **lang 모듈 미구현, 분리.** 로케일/문자열 처리는 리액터블 라이브러리와
|
10. **lang 모듈 미구현, 분리.** 로케일/문자열 처리는 리액터블 라이브러리와
|
||||||
별개 라이브러리로 존재해야 함(v1 `lang.lua`의 전역 스코프 버그 등은
|
별개 라이브러리로 존재해야 함(v1 `lang.lua`의 전역 스코프 버그 등은
|
||||||
`base/quad-v1-architecture.md` 참고 — 애초에 반면교사).
|
`reference/quad-v1-architecture.md` 참고 — 애초에 반면교사).
|
||||||
11. **커스텀 Signal 클래스 미구현.** 콜백 정도로 이벤트 바인드 뒤에 함수를
|
11. **커스텀 Signal 클래스 미구현.** 콜백 정도로 이벤트 바인드 뒤에 함수를
|
||||||
넣는 것만으로 충분하다고 판단. (이전 초안엔 "rbvm의 Signal이 재사용
|
넣는 것만으로 충분하다고 판단. (이전 초안엔 "rbvm의 Signal이 재사용
|
||||||
가능해 보여 상충한다"는 메모가 있었으나 2026-08-04 검증 라운드에서 최종
|
가능해 보여 상충한다"는 메모가 있었으나 2026-08-04 검증 라운드에서 최종
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ Signal 미채택, Ref 역할)과 소스 트리 상 패키지 경계(디스패치
|
||||||
array API, `CreatedRef` 모양) 뿐 — 구현 단계에서 자연히 정리됨. 원본:
|
array API, `CreatedRef` 모양) 뿐 — 구현 단계에서 자연히 정리됨. 원본:
|
||||||
`.claude/initreq/raw-userinput.md`
|
`.claude/initreq/raw-userinput.md`
|
||||||
"key와 value에 대한 바인드 연산은 pluggable 하도록 구성하기" / "스토어는 스토어를
|
"key와 value에 대한 바인드 연산은 pluggable 하도록 구성하기" / "스토어는 스토어를
|
||||||
저장 가능한가" / "Ref는 고민중" 절. v1의 문제점은 `base/quad-v1-architecture.md`
|
저장 가능한가" / "Ref는 고민중" 절. v1의 문제점은 `reference/quad-v1-architecture.md`
|
||||||
("ProcessQuadProperty" 하드코딩 디스패처), 참고 패턴은 `.claude/initreq/tbox`
|
("ProcessQuadProperty" 하드코딩 디스패처), 참고 패턴은 `.claude/initreq/tbox`
|
||||||
(레지스트리)와 Fusion/Vide 비교는 `base/comparison-fusion-vide.md` 참고.
|
(레지스트리)와 Fusion/Vide 비교는 `reference/comparison-fusion-vide.md` 참고.
|
||||||
|
|
||||||
## 문제
|
## 문제
|
||||||
|
|
||||||
|
|
@ -352,7 +352,7 @@ flatten된 값은 해시 파트(프로퍼티 키)로 존재하게 되고, Store
|
||||||
## 이벤트 핸들러는 self(Instance)를 받지 않는다 — 확정 (2026-08-06)
|
## 이벤트 핸들러는 self(Instance)를 받지 않는다 — 확정 (2026-08-06)
|
||||||
|
|
||||||
**결정**: v1의 `function(self, ...)` 관습(`self`/`this`로 이벤트 대상
|
**결정**: v1의 `function(self, ...)` 관습(`self`/`this`로 이벤트 대상
|
||||||
Instance를 넘겨주는 것, `.claude/base/quad-v1-architecture.md` 참고 —
|
Instance를 넘겨주는 것, `.claude/reference/quad-v1-architecture.md` 참고 —
|
||||||
실제로 `event.lua`의 `Bind`가 `func(self or this, ...)`로 넘겨줌)은
|
실제로 `event.lua`의 `Bind`가 `func(self or this, ...)`로 넘겨줌)은
|
||||||
**채택하지 않는다.** quad-roblox의 이벤트 핸들러는 엔진이 네이티브로
|
**채택하지 않는다.** quad-roblox의 이벤트 핸들러는 엔진이 네이티브로
|
||||||
주는 이벤트 인자만 받는다(React의 `onXxx`가 DOM 노드가 아니라
|
주는 이벤트 인자만 받는다(React의 `onXxx`가 DOM 노드가 아니라
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ Store/State/Source 온톨로지가 먼저 확정된 뒤에야 이 논의가 열
|
||||||
## 문제
|
## 문제
|
||||||
|
|
||||||
v1의 `Class.Extend()`(Init/Render/AfterRender/Getter/Setter/UpdateTriggers,
|
v1의 `Class.Extend()`(Init/Render/AfterRender/Getter/Setter/UpdateTriggers,
|
||||||
`base/quad-v1-architecture.md` 참고)는 이미 OOP 상속 스타일이라 폐기
|
`reference/quad-v1-architecture.md` 참고)는 이미 OOP 상속 스타일이라 폐기
|
||||||
방향이지만, 그게 제공하던 실제 편의 기능(컴포넌트가 자기 store를 자동으로
|
방향이지만, 그게 제공하던 실제 편의 기능(컴포넌트가 자기 store를 자동으로
|
||||||
가짐, props로 넘어온 State를 자동 흡수, `self:Default`/`self "key"`로
|
가짐, props로 넘어온 State를 자동 흡수, `self:Default`/`self "key"`로
|
||||||
기본값·바인딩)까지 같이 버려도 되는지가 미결이었음. `MyComp {...}` 형태로
|
기본값·바인딩)까지 같이 버려도 되는지가 미결이었음. `MyComp {...}` 형태로
|
||||||
|
|
@ -56,42 +56,30 @@ State는 `:With`/`:Compute`로 만들어진 파생값일 수 있어 쓰기가
|
||||||
의미 있음 — **사용자 확정**("맞음. 확실해"). 이 원칙 자체는 그대로 유지되고,
|
의미 있음 — **사용자 확정**("맞음. 확실해"). 이 원칙 자체는 그대로 유지되고,
|
||||||
아래 3번의 구체적 메커니즘만 2026-08-06 후속 세션에서 더 단순하게 갱신됨.
|
아래 3번의 구체적 메커니즘만 2026-08-06 후속 세션에서 더 단순하게 갱신됨.
|
||||||
|
|
||||||
### 3. [정정, 2026-08-06 후속 세션] `StoreSource` 프록시 개념 폐기 — Source가 State를 구조적으로 만족하므로 Store가 내부 Source를 그대로 반환
|
### 3. Store는 내부 Source를 그대로 반환 — Source가 State를 구조적으로 만족
|
||||||
|
|
||||||
**원래 이 절은 "Source를 인터페이스+구현체로 두고 Store 키에서 얇은 프록시
|
**확정**: `Source<T>`가 구조적으로 `State<T>`를 만족하므로(단방향 호환,
|
||||||
(`StoreSource`)를 받는다"는 방향이었음 — 지금은 폐기됨.** 이후 세션에서
|
Svelte `Writable<T> extends Readable<T>`와 같은 모양), `store.key`는 Store
|
||||||
Store/Source dot-access 타입 문제(레코드 타입의 읽기/쓰기 비대칭)를
|
생성 시 이미 만들어둔 진짜 Source 객체를 그대로 반환한다 — 별도 프록시
|
||||||
다루다가 더 근본적인 재구성으로 수렴: **`Source<T>`가 구조적으로
|
타입도, 별도 캐싱 계층도 없음(Source 자체가 이미 State의 읽기 계약을
|
||||||
`State<T>`를 만족**(단방향 호환, Svelte `Writable<T> extends Readable<T>`와
|
전부 만족하고 거기에 `:Set(value)`/`:Emit()`이 추가로 있을 뿐이라 "원본이라
|
||||||
같은 모양)하도록 만들면, Store가 "내부 Source를 감추고 별도 프록시를
|
쓰기 가능"이라는 위 2번 규칙과도 자연히 맞아떨어짐). 쓰기 문법도 같이
|
||||||
새로 만들어 노출"할 이유 자체가 없어짐 — `store.key`가 Store 생성 시
|
바뀜: `store.key = v`가 아니라 `store.key:Set(v)`(레코드 타입 읽기/쓰기
|
||||||
이미 만들어둔 진짜 Source 객체를 그대로 돌려줘도 안전함(Source 자체가
|
대칭 + lazy 동작에 `=`가 암시하는 "즉시 커밋"이 안 맞는다는 논거). 상세
|
||||||
이미 State의 읽기 계약을 전부 만족하고, 거기에 `:Set(value)`/`:Emit()`이
|
근거·타입 설계·Luau 솔버 검증 필요 항목은 `base/store-semantics.md`의
|
||||||
추가로 있을 뿐이라 "원본이라 쓰기 가능"이라는 위 2번 규칙과도 자연히
|
"Source가 State를 만족함" 절이 최종 소스.
|
||||||
맞아떨어짐). 상세 근거·타입 설계·Luau 솔버 검증 필요 항목은
|
|
||||||
`base/store-semantics.md`의 "Source가 State를 만족함" 절이 최종 소스 —
|
|
||||||
이 문서는 배경만 유지.
|
|
||||||
|
|
||||||
- **쓰기 문법도 같이 바뀜**: `store.key = v`가 아니라 `store.key:Set(v)`
|
**[이전에 확정했다가 폐기된 `StoreSource` 프록시 설계는 이 결론으로
|
||||||
(레코드 타입 읽기/쓰기 대칭 + lazy 동작에 `=`가 암시하는 "즉시 커밋"이
|
완전히 대체됨 — 원문·역전 이유·신구 비교표는
|
||||||
안 맞는다는 논거, 같은 절 참고).
|
`archive/store-source-proxy-reversed.md` 참고, 여기서는 반복하지 않음.]**
|
||||||
- **캐시 문제도 이걸로 자연히 해소**: State를 "매번 새로 만듦"이던 이전
|
|
||||||
모델과 달리, 이제 Store는 생성 시 만들어둔 Source를 그대로 갖고 있다가
|
|
||||||
돌려주기만 하므로 별도 캐싱 메커니즘 자체가 불필요(래퍼 생성 단계가
|
|
||||||
아예 없어짐 — 이전보다 더 쌈).
|
|
||||||
|
|
||||||
### 4. [정정, 2026-08-06 후속 세션] Source 직접 전달 — 타입 유니온도 불필요해짐
|
### 4. Source 직접 전달 — 타입 유니온 불필요, 서브타입 호환으로 자동 통과
|
||||||
|
|
||||||
원래 "핸들러가 `Source<T> | State<T>` 유니온으로 받는다"는 방향이었으나,
|
핸들러는 `State<T>` 하나만 받아도 Source 인스턴스가 서브타입 호환으로
|
||||||
Source가 State를 구조적으로 만족하는 지금은 **유니온 자체가 필요 없음** —
|
자동 통과된다(`Source<T> | State<T>` 유니온 불필요, `isHandlable`/
|
||||||
핸들러는 그냥 `State<T>` 하나만 받아도 Source 인스턴스가 자동으로 그
|
`priority`/`process`/`retract` 4종 계약에 5번째 항목 추가 불필요). 런타임에
|
||||||
자리에 들어감(서브타입 호환). `isHandlable`/`priority`/`process`/`retract`
|
"이게 Source면 역방향 쓰기까지 걸고 싶다"처럼 구분하고 싶은 경우는
|
||||||
4종 계약에 5번째 항목을 추가할 필요 없다는 결론은 그대로 유지, 다만 근거가
|
`isSource`류 판별자로(`isObserver`와 동일한 패턴).
|
||||||
"타입 유니온으로 처리"에서 "서브타입이라 유니온 자체가 불필요"로 더
|
|
||||||
단순해짐. 단, 핸들러가 "이거 Source면 역방향 쓰기까지 걸고 싶다"처럼
|
|
||||||
**런타임에** Source인지 구분하고 싶은 경우는 여전히 있을 수 있음 —
|
|
||||||
그건 타입 유니온이 아니라 런타임 판별자(`isSource`류, `isObserver`
|
|
||||||
패턴과 동일한 결)로 처리하면 됨.
|
|
||||||
|
|
||||||
- **실사용 범위가 좁다는 판단은 그대로 유지**: `isEnabled`처럼 여러 조건에
|
- **실사용 범위가 좁다는 판단은 그대로 유지**: `isEnabled`처럼 여러 조건에
|
||||||
영향받는(=파생된) 값은 애초에 State지 Source가 아니므로 이 경로로 못
|
영향받는(=파생된) 값은 애초에 State지 Source가 아니므로 이 경로로 못
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ canExecute 같은 람다 함수 하나 달게 해서 Connected 상태 보게 하
|
||||||
|
|
||||||
즉, 핸들러가 처리 도중 만든 구독/옵저버 클로저는 그 자체로는 아무것도 자동으로
|
즉, 핸들러가 처리 도중 만든 구독/옵저버 클로저는 그 자체로는 아무것도 자동으로
|
||||||
GC에 묶이지 않음 — v1이 여기저기서 `PropertyChangedSignal`에 연결해 참조를
|
GC에 묶이지 않음 — v1이 여기저기서 `PropertyChangedSignal`에 연결해 참조를
|
||||||
붙잡아두던 "GC 방지 핫팩"(`base/quad-v1-architecture.md` 참고)과 같은 문제.
|
붙잡아두던 "GC 방지 핫팩"(`reference/quad-v1-architecture.md` 참고)과 같은 문제.
|
||||||
**base가 범용 유틸로 제공할 것: 임의의 클로저/구독을 실제 Roblox 객체의
|
**base가 범용 유틸로 제공할 것: 임의의 클로저/구독을 실제 Roblox 객체의
|
||||||
생명주기에 바인드하는 도구** — 내부적으로 v1/rbvm이 쓰던 "connect 트릭"(어떤
|
생명주기에 바인드하는 도구** — 내부적으로 v1/rbvm이 쓰던 "connect 트릭"(어떤
|
||||||
신호에든 연결해서 참조를 죽을 때까지만 붙잡아두는 것)을 그대로 재사용. 이
|
신호에든 연결해서 참조를 죽을 때까지만 붙잡아두는 것)을 그대로 재사용. 이
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ PA님 방식인 문자열 키 + 런타임 리플렉션으로 감, `base/bind-sys
|
||||||
|
|
||||||
`Modifier.Rounded(8)`가 실제로 어떻게 UICorner 자식을 만들어 붙이는지(v1의
|
`Modifier.Rounded(8)`가 실제로 어떻게 UICorner 자식을 만들어 붙이는지(v1의
|
||||||
`Corner` 특수 프로퍼티 선례, 핸들러 배치 소견)는
|
`Corner` 특수 프로퍼티 선례, 핸들러 배치 소견)는
|
||||||
`research/ui-shorthand-plan.md` 참고 — 이 문서는 Modifier 값 자체의
|
`base/ui-shorthand-plan.md` 참고 — 이 문서는 Modifier 값 자체의
|
||||||
동작만 다루므로 분리.
|
동작만 다루므로 분리.
|
||||||
|
|
||||||
### 6. State/Pipe 쪽엔 영향 없음 — 이미 있던 결정의 재확인일 뿐
|
### 6. State/Pipe 쪽엔 영향 없음 — 이미 있던 결정의 재확인일 뿐
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
소스 트리 상 패키지 경계까지 확정되어 `research/`에서 승격됨(`base/
|
소스 트리 상 패키지 경계까지 확정되어 `research/`에서 승격됨(`base/
|
||||||
architecture.md`의 "구현 착수: 소스 트리 구조 확정" 절 참고). 원본:
|
architecture.md`의 "구현 착수: 소스 트리 구조 확정" 절 참고). 원본:
|
||||||
`.claude/initreq/raw-userinput.md` "slot을 구현하도록 하기로 했음" 절. Fusion의
|
`.claude/initreq/raw-userinput.md` "slot을 구현하도록 하기로 했음" 절. Fusion의
|
||||||
`Children` SpecialKey와 Vide의 mount 무가드 비교는 `base/comparison-fusion-vide.md`
|
`Children` SpecialKey와 Vide의 mount 무가드 비교는 `reference/comparison-fusion-vide.md`
|
||||||
참고 — 결론: **두 라이브러리 어디에도 이런 엄격한 단일 마운트 가드가 없음,
|
참고 — 결론: **두 라이브러리 어디에도 이런 엄격한 단일 마운트 가드가 없음,
|
||||||
quad의 진짜 개선점.**
|
quad의 진짜 개선점.**
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ InstanceChild.luau`. Slot은 "뮤터블 배열"을 다루고 이 핸들러는 "
|
||||||
Slot에 들어간 요소는 **ownership이 귀속**되며 다른 곳에 마운트할 수 없게 된다.
|
Slot에 들어간 요소는 **ownership이 귀속**되며 다른 곳에 마운트할 수 없게 된다.
|
||||||
`isMounted`를 관리해서, **한 인스턴스에 대한 다중 마운팅이 라이브러리 차원에서
|
`isMounted`를 관리해서, **한 인스턴스에 대한 다중 마운팅이 라이브러리 차원에서
|
||||||
절대 일어나지 않도록 강제**하는 게 v1 대비 핵심 디자인 변화. v1의 `mount()`는
|
절대 일어나지 않도록 강제**하는 게 v1 대비 핵심 디자인 변화. v1의 `mount()`는
|
||||||
별다른 강제를 안 했지만(`base/quad-v1-architecture.md`의 mount.lua 분석 참고 —
|
별다른 강제를 안 했지만(`reference/quad-v1-architecture.md`의 mount.lua 분석 참고 —
|
||||||
실제로는 부모/자식 부기까지 했지만 다중 마운트 방지는 없었음), v2는 mount
|
실제로는 부모/자식 부기까지 했지만 다중 마운트 방지는 없었음), v2는 mount
|
||||||
함수 자체가 이 강제를 담당.
|
함수 자체가 이 강제를 담당.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ Source가 State 계약을 만족하는 이상 같은 이유(Modifier용 processo
|
||||||
|
|
||||||
`useEffect`처럼 여러 store 값을 디펜던시로 묶어 파생값을 계산하고 싶다는
|
`useEffect`처럼 여러 store 값을 디펜던시로 묶어 파생값을 계산하고 싶다는
|
||||||
요구가 있었음(v1의 `myStore "a,b"` 콤마-조인 문자열 방식은 폐기 대상 —
|
요구가 있었음(v1의 `myStore "a,b"` 콤마-조인 문자열 방식은 폐기 대상 —
|
||||||
`base/quad-v1-architecture.md`의 "문자열 DSL" 문제점 참고). **v1의
|
`reference/quad-v1-architecture.md`의 "문자열 DSL" 문제점 참고). **v1의
|
||||||
`:Add`/`:With`/`:Tween`처럼 값을 직접 가공하는 이름 붙은(named) 체이닝 연산은
|
`:Add`/`:With`/`:Tween`처럼 값을 직접 가공하는 이름 붙은(named) 체이닝 연산은
|
||||||
만들지 않음** — 대신 일반 함수를 받아 처리. (주의: 아래의 v2 `:With(...)`는
|
만들지 않음** — 대신 일반 함수를 받아 처리. (주의: 아래의 v2 `:With(...)`는
|
||||||
이름만 같을 뿐 v1의 `:With`와는 다른 연산임 — v1은 "함수/테이블에서 값을
|
이름만 같을 뿐 v1의 `:With`와는 다른 연산임 — v1은 "함수/테이블에서 값을
|
||||||
|
|
@ -291,3 +291,12 @@ Source가 State 계약을 만족하는 이상 같은 이유(Modifier용 processo
|
||||||
`:Compute(fn)`으로 파생 State를 만드는 것으로 확정 — `Store.Combine({a,b},
|
`:Compute(fn)`으로 파생 State를 만드는 것으로 확정 — `Store.Combine({a,b},
|
||||||
fn)`류 포지셔널 인자 방식은 기각됨. 정확한 lazy 인자 규칙(self/with 값 둘 다
|
fn)`류 포지셔널 인자 방식은 기각됨. 정확한 lazy 인자 규칙(self/with 값 둘 다
|
||||||
State 핸들로 넘기고 `:Get()`을 실제로 읽을 때만 계산)은 `base/bind-system-plan.md`의 "Store/State/Source 온톨로지" 절 참고.
|
State 핸들로 넘기고 `:Get()`을 실제로 읽을 때만 계산)은 `base/bind-system-plan.md`의 "Store/State/Source 온톨로지" 절 참고.
|
||||||
|
|
||||||
|
**여러 소스를 한 번에 바꿔도 파생값 재계산/재대입이 한 번만 되게 하려면
|
||||||
|
`Blocker` 참고.** 위 `:With`+`:Compute`만으로는 "state1, state2를 연달아
|
||||||
|
Set하면 결합된 파생값이 두 번 재계산/재대입된다"는 문제(즉시 pull하는
|
||||||
|
store-bind 소비자 기준)는 안 풀림 — 이건 별도 확정 프리미티브
|
||||||
|
`base/additional-primitives.md`의 "Blocker" 절이 다룸(State 개발과 같은
|
||||||
|
마일스톤, `ROADMAP.md` M3에서 함께 구현). lexical `Batch(fn)`으로 풀려던
|
||||||
|
초기 시도는 코루틴 yield 위에서 구조적으로 위험해 기각됨 —
|
||||||
|
`archive/batch-rejected.md` 참고.
|
||||||
|
|
|
||||||
108
.claude/base/ui-shorthand-plan.md
Normal file
108
.claude/base/ui-shorthand-plan.md
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
# UI 편의 숏핸드 (UICorner/UIPadding/UIScale) — 인라인 적용
|
||||||
|
|
||||||
|
**상태**: base — 기능 필요 여부·이름·메커니즘·패키지 배치·store-bind 가능성까지
|
||||||
|
전부 확정(2026-08-07 문서 정리에서 `research/`→`base/` 승격). 남은 건 구현
|
||||||
|
단계의 세부 시그니처뿐.
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
|
||||||
|
사용자 기억: v1을 쓸 때 "UICorner/UIPadding/UIScale 같은 걸 직접
|
||||||
|
`Instance.new`로 만들어 Parent하는 귀찮은 작업 없이, Frame 안에 인라인으로
|
||||||
|
넣기만 해도 CSS 스타일처럼 적용됐다 — 코드가 줄고 읽기도 편해서 꽤
|
||||||
|
괜찮았다"는 것. v1 소스(`.claude/initreq/quad`)와 PA님 코드
|
||||||
|
(`.claude/initreq/artworks`)를 서브에이전트로 조사해 확인.
|
||||||
|
|
||||||
|
## v1 실제 메커니즘 (조사 완료)
|
||||||
|
|
||||||
|
`class.lua`의 `SetProperty`/`GetProperty`(38~109행)와 `ProcessQuadProperty`
|
||||||
|
(134~213행)에 하드코딩된 if/elseif 분기로 특수 문자열 키 5종을 지원했음 —
|
||||||
|
`Corner = 8` → 숫자 하나, 기존 `UICorner` 자식이 있으면 재사용, 없으면
|
||||||
|
`Instance.new("UICorner", item)`으로 생성(`Name = "_quad_round"`),
|
||||||
|
`CornerRadius = UDim.new(0, value)` 설정. `PaddingAll`/`PaddingAllOffset`,
|
||||||
|
`Scale`도 동일 패턴(`UIPadding`/`UIScale`, `_quad_padding`/`_quad_scale`).
|
||||||
|
값 모양은 항상 **리터럴 하나**(숫자/UDim) — 테이블도 `__type` 태그도 아님.
|
||||||
|
v1엔 이 5종과 별개로 `RoundSize`(이미지 9-slice 라운드 트릭, UICorner와는
|
||||||
|
전혀 다른 메커니즘)도 있었으나 **이건 드롭 확정** — 자세한 사유는
|
||||||
|
`archive/ui-shorthand-roundsize-dropped.md` 참고, 이 문서에서는 반복하지
|
||||||
|
않음.
|
||||||
|
|
||||||
|
**`UIListLayout`/`UIGridLayout`/flex 전용 숏핸드는 v1에 없었음** —
|
||||||
|
`ProcessQuadProperty`의 범용 자식 나열 분기(배열 인덱스로 놓인 Instance/
|
||||||
|
Class 결과를 자동 mount)로 `UIListLayout{...}`을 그냥 직접 나열했을 뿐,
|
||||||
|
`List = true` 같은 전용 축약 문법은 레포 전체(PA님 코드 포함)에서 찾지
|
||||||
|
못했음. quad-v2도 이 부분은 이미 있는 children-array + 인스턴스 생성
|
||||||
|
문법으로 그대로 커버됨 — 새로 설계할 것 없음.
|
||||||
|
|
||||||
|
## 결론 — 이름은 UICorner/UIPadding/UIScale로 확정 (프리픽스 필요)
|
||||||
|
|
||||||
|
**기능은 여전히 필요**: `UICorner`가 Roblox 네이티브 Instance가 됐어도
|
||||||
|
"별도 Instance를 만들어 부모에 Parent해야 한다"는 구조적 번거로움 자체는
|
||||||
|
없어지지 않으므로, 이 숏핸드의 존재 이유는 그대로 유효 — **사용자
|
||||||
|
재확정**("UIScale 같은 건 여전히 별도의 Instance고 부모 Frame에 영향을
|
||||||
|
주는 구조, 숏핸드는 여전히 필요하다").
|
||||||
|
|
||||||
|
**이름은 v1의 `Corner`/`PaddingAll`/`Scale`을 그대로 안 가져오고 실제
|
||||||
|
Roblox Instance 이름과 맞춘 `UICorner`/`UIPadding`(+`UIPaddingOffset`)/
|
||||||
|
`UIScale`로 확정** — v1식 짧은 이름을 그대로 쓰면 Modifier 체이닝
|
||||||
|
메소드(`mod:Corner(8)`)가 "진짜 UICorner를 만드는 숏핸드"인지 그냥 우연히
|
||||||
|
비슷한 이름의 부가 Modifier 필드인지 구분이 안 됨(사용자 지적). 접두어
|
||||||
|
`UI`를 붙이면 실제 대응하는 Roblox Instance 클래스 이름과 1:1로 읽혀서
|
||||||
|
이 모호함 자체가 사라짐 — `Frame { UICorner = 8 }`, `mod:UICorner(8)`.
|
||||||
|
|
||||||
|
## 메커니즘 — 새 아키텍처 개념 불필요
|
||||||
|
|
||||||
|
이미 있는 pluggable Handler로 그대로 커버됨. `UICorner`/`UIPadding`/
|
||||||
|
`UIScale` 같은 특수 키를 인식하는 Handler(`isHandlable`이 그 키를 매칭)가
|
||||||
|
"이름 붙은 자식을 찾거나 만들고 프로퍼티 세팅"을 `process(inst, k, v)`에
|
||||||
|
구현 — v1의 하드코딩 if/elseif 대신 정식 핸들러 계약(`isHandlable`/
|
||||||
|
`priority`/`process`/`retract`)을 따르는 것만 다름. `modifier-plan.md`가
|
||||||
|
이미 예시로 든 `Modifier.Rounded(8)`은 이 특수 키를 flatten해서 props에
|
||||||
|
꽂아넣는 사탕 문법일 뿐, 실제 처리는 이 Handler가 함 — Modifier를 안 거치고
|
||||||
|
`Frame { UICorner = 8 }`처럼 순수 인라인 키로 직접 써도(v1처럼) 동일하게
|
||||||
|
작동함, `architecture.md`의 `[Attribute "Name"]`류 특수 키와 같은 층위.
|
||||||
|
자동 생성된 자식은 기존 관례대로 `_`/`QUAD_` 접두어 네이밍
|
||||||
|
(`research/debug-tooling-plan.md` 9번, v1의 `_quad_round`류 그대로 재사용).
|
||||||
|
|
||||||
|
**기존 자식과의 매칭 기준(2026-08-06 감사에서 지적된 항목, 여기서 같이
|
||||||
|
확정)**: 재사용 대상은 quad가 이전에 만든 고정 이름(`_quad_corner`류)
|
||||||
|
자식으로 한정 — 타입만 보고(`UICorner`이기만 하면 아무거나) 재사용하지
|
||||||
|
않음. 사용자가 직접 만든 `UICorner`를 quad가 멋대로 건드리는 부작용을
|
||||||
|
피하기 위함.
|
||||||
|
|
||||||
|
## store-bind — 이 숏핸드도 지원, Tween만큼 무겁게 안 가도 됨
|
||||||
|
|
||||||
|
v1에서도 `Corner`/`PaddingAll`/`Scale`은 store 값으로 바인드 가능했음
|
||||||
|
(`myStore "key"` 체이닝으로 다른 프로퍼티와 동일하게 취급됨) — quad-v2도
|
||||||
|
이 능력을 유지한다. 트윈처럼 애니메이션까지 지원할 필요는 없음(API 표면만
|
||||||
|
복잡해짐) — 그냥 값이 바뀌면 `CornerRadius`/`Padding`/`Scale` 프로퍼티를
|
||||||
|
다시 세팅하는 정도로 충분. 구현 비용도 낮음: 각 Handler가 `process`에서
|
||||||
|
"이전에 자기가 찾거나 만든 자식 Instance"를 얻어야 하는데, 이건 이미
|
||||||
|
base가 범용 유틸로 제공하기로 확정한 per-instance weak-keyed 저장소
|
||||||
|
(`base.perInstanceState(inst)`, `base/bind-system-plan.md` "핸들러 내부
|
||||||
|
상태 저장" 절)를 그대로 재사용하면 됨 — Tween 핸들러가 실행 중인 Tween
|
||||||
|
객체를 기억해두는 것과 정확히 같은 패턴. 새 메커니즘 발명 불필요, 이미
|
||||||
|
있는 "store 바인드는 pluggable 바인드를 재실행하는 래핑" 원칙
|
||||||
|
(`base/bind-system-plan.md` "확정된 디스패치 모델" 절)이 그대로 적용됨.
|
||||||
|
|
||||||
|
## 패키지 배치 — `quad-roblox` 코어에 직접 포함, 확정
|
||||||
|
|
||||||
|
"트윈도 인스턴스 생성/제어를 직접 구현 가능한 걸 하나로 묶어 쉽게 쓰게
|
||||||
|
합친 것 — 너무 잘게 쪼개 오버엔지니어링하기보다 확실히 하나로 코어에
|
||||||
|
넣어도 충분하다, opt-out할 이유가 별로 없다"는 게 사용자 판단 — **작고
|
||||||
|
항상 켜져 있어도 비용이 무시할 만한 편의 기능은 별도 opt-out 패키지로
|
||||||
|
쪼개지 말고 `quad-roblox` 코어에 직접 포함한다**는 원칙으로 확정(이미
|
||||||
|
계획된 Tween 핸들러가 같은 모양이라는 게 근거). 이 원칙은 일반화해서
|
||||||
|
재사용 가능 — 앞으로 비슷한 "작은 인스턴스 편의 기능"이 제안되면
|
||||||
|
`quad-roblox-util` 같은 걸 새로 만들지 않고 이 선례를 따르면 됨.
|
||||||
|
|
||||||
|
**중요도**: 낮음("이건 나중에도 쉽게 구현됨" — 사용자) — 지금 M0 우선순위를
|
||||||
|
바꿀 이유는 없음, M10(Handlers/Attribute 등) 전후로 다른 세부 Handler와
|
||||||
|
함께 구현하면 충분.
|
||||||
|
|
||||||
|
## 남은 열린 질문 (단순화 후보, 사소함)
|
||||||
|
|
||||||
|
- Corner/PaddingAll/Scale 3개 거의 동일한 형태의 Handler를 각각 만들지,
|
||||||
|
`{key -> {ChildClassName, ChildDefaultName, Property, wrap=fn}}` 룩업
|
||||||
|
테이블로 구동되는 단일 `Handlers/InstanceShorthand.luau`로 통합할지 —
|
||||||
|
`research/pre-implementation-audit.md` 3-2번 참고, 강제 사항 아님,
|
||||||
|
구현 시점에 결정할 정도의 사소한 개선 후보.
|
||||||
|
|
@ -14,8 +14,11 @@
|
||||||
|
|
||||||
사용자 질문: "다른 독립 프리미티브나 종속 파생 데이터는 뭐가 더 필요할 것
|
사용자 질문: "다른 독립 프리미티브나 종속 파생 데이터는 뭐가 더 필요할 것
|
||||||
같나요. 이것만으로 이 프로젝트는 충분하다 생각해요?" — 여러 서브에이전트
|
같나요. 이것만으로 이 프로젝트는 충분하다 생각해요?" — 여러 서브에이전트
|
||||||
조사 + 사용자와 라이브 논의로 계속 수렴 중, 상세는 `research/
|
조사 + 사용자와 라이브 논의로 계속 수렴 중. **2026-08-07 문서 정리에서
|
||||||
additional-primitives-plan.md`. 요지:
|
확정/기각된 항목은 `research/additional-primitives-plan.md`에서
|
||||||
|
분리됨**: Effect/Blocker → `base/additional-primitives.md`, Batch →
|
||||||
|
`archive/batch-rejected.md`, Context(+레이어드 Store) → `archive/
|
||||||
|
context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만** 남김.
|
||||||
|
|
||||||
- **키 기반 동적 컬렉션 재조정(유일하게 아직 완전히 열려있음, 최우선)**:
|
- **키 기반 동적 컬렉션 재조정(유일하게 아직 완전히 열려있음, 최우선)**:
|
||||||
Fusion `ForPairs`/`ForKeys`/`ForValues`, Vide `indexes()`/`values()`,
|
Fusion `ForPairs`/`ForKeys`/`ForValues`, Vide `indexes()`/`values()`,
|
||||||
|
|
@ -26,33 +29,20 @@ additional-primitives-plan.md`. 요지:
|
||||||
못 쓴다는 반례로 철회됨), Slot에 파괴 없이 빼내는 `Extract` 연산 추가
|
못 쓴다는 반례로 철회됨), Slot에 파괴 없이 빼내는 `Extract` 연산 추가
|
||||||
필요 — 최종 이름만 미정(아래 "용어 정리" 절에 후보 추가). **사용자가
|
필요 — 최종 이름만 미정(아래 "용어 정리" 절에 후보 추가). **사용자가
|
||||||
"작업 전에 모든 정의를 마치고 싶다"고 명시** — M0 이전 완전 확정 목표.
|
"작업 전에 모든 정의를 마치고 싶다"고 명시** — M0 이전 완전 확정 목표.
|
||||||
- **Effect — 거의 수렴**: leaf가 죽을 때 확정적으로 정리 콜백을 부르는
|
상세는 `research/additional-primitives-plan.md`(이제 이 주제 전용).
|
||||||
단순 primitive(재실행 개념 없음, Observer와 별개)로 합의, 시그니처만
|
- **Effect가 Observer의 변형(`state:Effect()`)인지, 완전히 독립된 free
|
||||||
남음. Observer에 cleanup 반환 계약을 얹는 안은 기각(클로저 업밸류로
|
function인지 — 신규, 2026-08-07 문서 정리 세션에서 발견.** `base/
|
||||||
이미 충분 — `pre-implementation-audit.md` 3-1과 같은 논리).
|
additional-primitives.md`가 지금까지의 조사대로 Effect를 "재실행 없는
|
||||||
- **Blocker — 채택, 핵심 메커니즘+이름 확정(2026-08-07 신설)**: 여러
|
독립 free function"으로 서술해뒀지만, 사용자가 직접 `state:Effect()`
|
||||||
Source를 한꺼번에 바꿔도 파생값 재계산/재대입이 한 번만 되게 하는
|
형태(=Observer에 "확정 정리" 계약만 추가된 변형)로 기억하고 있어서
|
||||||
primitive — lexical Batch(아래 항목, 기각)와 달리 콜스택/코루틴이 아니라
|
확인이 필요함. 관련 하위 질문: `state:Observer(fn)`가 생성 시점에
|
||||||
**값**(`Blocker` 객체의 `On()`/`Off()`)으로 지연 구간을 표현해 코루틴
|
`fn`을 즉시 1회 실행하는지도 현재 문서 어디에도 명시돼 있지 않음(Effect는
|
||||||
yield 위험을 구조적으로 우회함. `state:Block(blocker) -> state`가
|
"즉시 1회 실행"이 스펙에 있음 — 이 부분만 보면 둘이 겹쳐 보이는 이유).
|
||||||
gated state를 반환(호출 즉시 onunblock 핸들 등록), `IsBlocked`/
|
**임의로 결론내지 않고 열어둠** — 구현 착수(M3~M4 전후) 전에 확인 필요,
|
||||||
`HasBlockedEmit` 필드, 재진입(네스팅)은 의도적으로 미지원(Rust
|
상세는 `base/additional-primitives.md`의 "미해결" 절.
|
||||||
poisoned-mutex류 위험 회피 — 겹치는 배치는 각자 새 `Blocker`를 쓸 것,
|
|
||||||
**문서화에서 강하게 명시 필요**). `base/`로 승격 가능한 수준, 남은 건
|
|
||||||
문서화뿐.
|
|
||||||
- **Batch(함수/코루틴 스코프 lexical block) — 기각 확정**: lexical
|
|
||||||
transaction 블록이 코루틴 yield 위에서 구조적으로 위험(전역/코루틴
|
|
||||||
스코프 플래그 둘 다 새 코루틴 스폰이나 영구 yield에 깨짐) — Blocker로
|
|
||||||
대체됐으므로 더 이상 미해결 문제 아님, quadnomicon "왜 Batch 대신
|
|
||||||
Blocker인가" 에세이 대상.
|
|
||||||
- **Context — 기각 확정, 대안이던 레이어드 Store도 철회**: 완전 자동
|
|
||||||
버전은 Roblox Luau 플랫폼 한계로 사실상 불가, 얕은 버전도 Slot 비동기
|
|
||||||
추가에서 조용히 깨짐 + quad-debug 철학과 충돌. 레이어드 Store 대안은
|
|
||||||
사용자 반박으로 철회 — 이미 있는 타입 강제 명시적 Store 전달 +
|
|
||||||
오버라이드 지점에서 새 Store를 만들어 넘기는 것으로 충분하다는 판단.
|
|
||||||
둘 다 "왜 없는가" quadnomicon 에세이 대상.
|
|
||||||
- Untrack/Suspense/Error Boundary/Readonly는 조사 결과 새 프리미티브 없이
|
- Untrack/Suspense/Error Boundary/Readonly는 조사 결과 새 프리미티브 없이
|
||||||
기존 설계·Lua 자체 기능으로 이미 충분한 것으로 판단.
|
기존 설계·Lua 자체 기능으로 이미 충분한 것으로 판단(`research/
|
||||||
|
additional-primitives-plan.md` "빈 자리 아닌 것" 절).
|
||||||
|
|
||||||
### 1. 용어 정리 (사용자 요청, 진행 중)
|
### 1. 용어 정리 (사용자 요청, 진행 중)
|
||||||
|
|
||||||
|
|
@ -95,7 +85,8 @@ additional-primitives-plan.md`. 요지:
|
||||||
사용자 피드백으로 탈락. 후보: `Render`(가장 직접적이지만 "quad엔 렌더
|
사용자 피드백으로 탈락. 후보: `Render`(가장 직접적이지만 "quad엔 렌더
|
||||||
주기가 없다"는 기존 원칙과 이름이 충돌해 보일 수 있음), `Draw`(짧지만
|
주기가 없다"는 기존 원칙과 이름이 충돌해 보일 수 있음), `Draw`(짧지만
|
||||||
즉시모드 GUI 뉘앙스), `List`(중립적이나 메커니즘을 안 알려줌) — 아직
|
즉시모드 GUI 뉘앙스), `List`(중립적이나 메커니즘을 안 알려줌) — 아직
|
||||||
미정, `research/additional-primitives-plan.md` 1번 절 참고.
|
미정, `research/additional-primitives-plan.md`의 "키 기반 동적 컬렉션
|
||||||
|
재조정" 절 참고.
|
||||||
- **"프로바이더"(3순위, 사소함)**: `base/module-lifecycle-plan.md`가
|
- **"프로바이더"(3순위, 사소함)**: `base/module-lifecycle-plan.md`가
|
||||||
"provider"라고 불러온, `isHandlable`로 참여 여부를 결정하고 우선순위대로
|
"provider"라고 불러온, `isHandlable`로 참여 여부를 결정하고 우선순위대로
|
||||||
스캔되는 pluggable 참가자 개념 — 정확한 이름을 "provider"/"processor"/
|
스캔되는 pluggable 참가자 개념 — 정확한 이름을 "provider"/"processor"/
|
||||||
|
|
@ -131,9 +122,10 @@ additional-primitives-plan.md`. 요지:
|
||||||
- **`canExecute`/`Connected`의 실제 구현 방식(Parent==nil vs Connection.
|
- **`canExecute`/`Connected`의 실제 구현 방식(Parent==nil vs Connection.
|
||||||
Connected vs Destroying 플래그)이 미확정인데 이미 Slot/Observer/store-bind
|
Connected vs Destroying 플래그)이 미확정인데 이미 Slot/Observer/store-bind
|
||||||
retract 전역에 재사용 확정됨** — M2/M3 착수 전 실측 필요 — 우선순위1-6.
|
retract 전역에 재사용 확정됨** — M2/M3 착수 전 실측 필요 — 우선순위1-6.
|
||||||
- **`LifetimeHandle` 인터페이스가 M8에 배치돼 있지만 M4/M6이 이미 그걸
|
- **~~`LifetimeHandle` 인터페이스가 M8에 배치돼 있지만 M4/M6이 이미 그걸
|
||||||
필요로 함(로드맵 순서 역전)** — quad-base 인터페이스 정의를 M2/M3로
|
필요로 함(로드맵 순서 역전)~~ — 반영 완료(2026-08-07 세 번째 세션)**:
|
||||||
옮기는 게 자연스러워 보임, `ROADMAP.md` 수정 필요 — 우선순위1-9.
|
`LifetimeHandle`/`PerInstanceState` 인터페이스(타입만)를 `ROADMAP.md`
|
||||||
|
M2로 옮기고, quad-roblox 실 구현만 M8에 남김 — 우선순위1-9 해소.
|
||||||
- 그 외(Slot CRUD 의미론 미정의, retract 시 "이전 핸들러" 추적 책임 소재,
|
- 그 외(Slot CRUD 의미론 미정의, retract 시 "이전 핸들러" 추적 책임 소재,
|
||||||
우선순위 스캔 동률/매치실패 처리, `:Compute`의 `previous` 인자가
|
우선순위 스캔 동률/매치실패 처리, `:Compute`의 `previous` 인자가
|
||||||
오버엔지니어링일 수 있음, UI shorthand의 기존 UICorner 매칭 기준 등)는
|
오버엔지니어링일 수 있음, UI shorthand의 기존 UICorner 매칭 기준 등)는
|
||||||
|
|
@ -141,10 +133,19 @@ additional-primitives-plan.md`. 요지:
|
||||||
|
|
||||||
### 3. 낮은 우선순위
|
### 3. 낮은 우선순위
|
||||||
|
|
||||||
|
- **`None`(가칭) 센티널 프리미티브 — 미확정, 2026-08-07 세 번째 세션
|
||||||
|
신설.** `{ Override = nil, mod }`처럼 인라인 키로 modifier가 주는 값을
|
||||||
|
명시적으로 지우고 싶어도 Lua 테이블 리터럴의 `키 = nil`은 키가 아예
|
||||||
|
없는 것과 구별이 안 돼서 "인라인이 modifier보다 무조건 우선"이라는
|
||||||
|
기존 merge 규칙(`modifier-plan.md` 2번)이 이 케이스에선 실제로 작동을
|
||||||
|
안 함. `false`를 이벤트 disconnect 센티널로 쓴 선례처럼 실재하는 값
|
||||||
|
`None`을 도입하는 방향만 나왔고 상세(타입, flatten 내부 표현, State
|
||||||
|
필드에도 같은 문제가 적용되는지)는 미정 — `modifier-plan.md` "2-1"절
|
||||||
|
참고, M7(Modifier) 착수 전 확인.
|
||||||
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
|
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
|
||||||
착수를 막지 않음.
|
착수를 막지 않음.
|
||||||
- **v1 `objectListClass.__newIndex` 오타 기능의 재현 테스트 필요** —
|
- **v1 `objectListClass.__newIndex` 오타 기능의 재현 테스트 필요** —
|
||||||
`base/quad-v1-architecture.md`에 남겨진 v1 내부 동작 확인 사항, 마이그레이션
|
`reference/quad-v1-architecture.md`에 남겨진 v1 내부 동작 확인 사항, 마이그레이션
|
||||||
가이드 작성 시점에 필요. 지금은 그냥 백로그로만 기록.
|
가이드 작성 시점에 필요. 지금은 그냥 백로그로만 기록.
|
||||||
- **여러 Slot이 형제로 섞일 때 순서 보장** — `base/slot-plan.md`의 "여러
|
- **여러 Slot이 형제로 섞일 때 순서 보장** — `base/slot-plan.md`의 "여러
|
||||||
Slot이 섞일 때 순서 보장" 절 참고. Roblox 단일 백엔드로는 급하지 않음
|
Slot이 섞일 때 순서 보장" 절 참고. Roblox 단일 백엔드로는 급하지 않음
|
||||||
|
|
@ -172,10 +173,6 @@ additional-primitives-plan.md`. 요지:
|
||||||
중 뭘로 갈지 — 소견은 DI 인스턴스 생성 패턴처럼 "제네릭 하나 + 자주
|
중 뭘로 갈지 — 소견은 DI 인스턴스 생성 패턴처럼 "제네릭 하나 + 자주
|
||||||
쓰는 타입만 정적 지름길" 절충이지만 확정 아님, M10(Handlers/Attribute)
|
쓰는 타입만 정적 지름길" 절충이지만 확정 아님, M10(Handlers/Attribute)
|
||||||
착수 전 아무 때나 확인해도 됨.
|
착수 전 아무 때나 확인해도 됨.
|
||||||
- **UICorner/UIPadding/UIScale 인라인 편의 키 세부** — `research/
|
|
||||||
ui-shorthand-plan.md`. 기능 필요 여부(여전히 필요로 재확정)·메커니즘
|
|
||||||
(Handler)·패키지 배치(quad-roblox 코어)는 확정, 남은 건 이름 재검토
|
|
||||||
(용어 정리 합류)와 `RoundSize`(이미지 라운드) 대체 방식뿐.
|
|
||||||
- **v1 하위호환(compat) 레이어 — `quad-roblox-v1-compat`** —
|
- **v1 하위호환(compat) 레이어 — `quad-roblox-v1-compat`** —
|
||||||
`research/v1-compat-plan.md`(신규, 2026-08-06, 두 차례 후속 논의로 수렴).
|
`research/v1-compat-plan.md`(신규, 2026-08-06, 두 차례 후속 논의로 수렴).
|
||||||
방향 확정: v1을 그대로 병행 실행 + 경계에서만 `state:Observer()`(lazy
|
방향 확정: v1을 그대로 병행 실행 + 경계에서만 `state:Observer()`(lazy
|
||||||
|
|
@ -203,8 +200,11 @@ additional-primitives-plan.md`. 요지:
|
||||||
| Modifier(정적 merge, immutable 체이닝, State 필드 지원) | `base/modifier-plan.md` |
|
| Modifier(정적 merge, immutable 체이닝, State 필드 지원) | `base/modifier-plan.md` |
|
||||||
| 컴포넌트화(플레인 함수, State/Source 경계, 컴포넌트 경계 modifier/Ref는 named parameter로 전달, multi-root 개념 폐기, `Modifier.Merge`) | `base/component-composition-plan.md` |
|
| 컴포넌트화(플레인 함수, State/Source 경계, 컴포넌트 경계 modifier/Ref는 named parameter로 전달, multi-root 개념 폐기, `Modifier.Merge`) | `base/component-composition-plan.md` |
|
||||||
| 컴포넌트 이식성(전역 store 참조 시 재사용성 문제) | `base/purity-and-effects-plan.md` |
|
| 컴포넌트 이식성(전역 store 참조 시 재사용성 문제) | `base/purity-and-effects-plan.md` |
|
||||||
| Fusion/Vide 비교 리서치(주의: 일부 서술은 이후 라운드에서 뒤집힘, 문서 내 정정 표시 참고) | `base/comparison-fusion-vide.md` |
|
| Blocker(값 기반 emit 지연/합치기), Effect(leaf 죽음에 확정 정리 — 단 Observer와의 관계는 위 0번 열린 질문 참고) | `base/additional-primitives.md` |
|
||||||
| v1 내부 동작 스냅샷 | `base/quad-v1-architecture.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` |
|
||||||
|
| v1 내부 동작 스냅샷 | `reference/quad-v1-architecture.md` |
|
||||||
| 트윈 오버라이드(기본값 Cancel), 세부 옵션만 남음 | `research/tween-plan.md` |
|
| 트윈 오버라이드(기본값 Cancel), 세부 옵션만 남음 | `research/tween-plan.md` |
|
||||||
| quad2-try(폐기된 이전 시도) 리서치 — OOP 상속/커스텀 파서/Slot 스텁/`Pipe` COW 전부 죽은 접근으로 확인, 반복 조사 금지 | `base/bind-system-plan.md` |
|
| quad2-try(폐기된 이전 시도) 리서치 — OOP 상속/커스텀 파서/Slot 스텁/`Pipe` COW 전부 죽은 접근으로 확인, 반복 조사 금지 | `base/bind-system-plan.md` |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
# Fusion / Vide 아키텍처 비교 — quad-v2 설계 근거
|
# Fusion / Vide 아키텍처 비교 — quad-v2 설계 근거
|
||||||
|
|
||||||
**상태**: base — 리서치 스냅샷(참고용 근거 자료), "완료" 개념 없음. quad-v2의
|
**상태**: reference — 온디맨드 참고 자료, "완료" 개념 없음. **[2026-08-07
|
||||||
Store/Slot/Tween/bind-dispatch 설계 결정에 인용되는 원본 비교 자료.
|
문서 정리에서 `base/`→`reference/`로 이동]** quad에 관한 결정 자체가 아니라
|
||||||
|
Fusion/Vide 리서치 스냅샷이라 항상 읽어야 하는 base 컨텍스트는 아님 —
|
||||||
|
`quadnomicon`(프레임워크 설계자용 심화 콘텐츠) 소재 후보이기도 함. quad-v2의
|
||||||
|
Store/Slot/Tween/bind-dispatch 설계 결정에 근거로 인용될 때만 열어볼 것,
|
||||||
|
실제 확정 사항은 인용하는 쪽 `base/` 문서가 소스.
|
||||||
|
|
||||||
## Fusion (`.claude/initreq/fusion/`)
|
## Fusion (`.claude/initreq/fusion/`)
|
||||||
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
# quad v1 내부 구조 (재작성 이전 기준선)
|
# quad v1 내부 구조 (재작성 이전 기준선)
|
||||||
|
|
||||||
**상태**: base — 참고용 스냅샷, "완료" 개념 없음. v1(`.claude/initreq/quad/`)이
|
**상태**: reference — 온디맨드 참고 자료, "완료" 개념 없음. **[2026-08-07
|
||||||
실제로 어떻게 동작하는지 정리한 문서로, v2 설계 시 "이 문제를 안 반복하려면"의
|
문서 정리에서 `base/`→`reference/`로 이동]** v1 자체에 대한 스냅샷일 뿐 v2의
|
||||||
기준선으로 계속 참조됨. 아래는 리서치 에이전트가 file:line까지 확인한 내용의 요약 —
|
결정 사항이 아니라서 항상 읽어야 하는 base 컨텍스트는 아님 — 다른 문서가
|
||||||
정확한 인용이 필요하면 `.claude/initreq/quad/src/*.lua` 원본을 볼 것.
|
"v1은 이랬는데"를 인용할 때만 열어볼 것. v2 설계 시 "이 문제를 안 반복하려면"의
|
||||||
|
기준선으로 근거 인용되는 용도는 그대로 유지(각 인용 지점은 여전히
|
||||||
|
`base/`에 있음, 이 문서는 그 인용의 원본 소스). 아래는 리서치 에이전트가
|
||||||
|
file:line까지 확인한 내용의 요약 — 정확한 인용이 필요하면
|
||||||
|
`.claude/initreq/quad/src/*.lua` 원본을 볼 것.
|
||||||
|
|
||||||
## 공개 API 개요
|
## 공개 API 개요
|
||||||
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
# 추가 프리미티브 필요성 조사 — 다른 프레임워크 대비 갭 분석
|
# 추가 프리미티브 필요성 조사 — 다른 프레임워크 대비 갭 분석
|
||||||
|
|
||||||
**상태**: research — 사용자와 라이브 논의로 수렴(2026-08-06~07). Context/
|
**상태**: research — 사용자와 라이브 논의로 대부분 수렴(2026-08-06~07),
|
||||||
Batch(lexical)는 **기각 확정**. **Blocker**(새 primitive, Batch의 대안으로
|
**2026-08-07 문서 정리에서 확정/기각된 항목을 분리**: Effect/Blocker →
|
||||||
채택)는 핵심 메커니즘+이름 확정, 문서화만 남음. 키 기반 동적 컬렉션
|
`base/additional-primitives.md`, Batch(lexical) → `archive/
|
||||||
재조정은 설계 진행 중(사용자가 "작업 전에 모든 정의를 마치고 싶다"고
|
batch-rejected.md`, Context(+레이어드 Store 대안) → `archive/
|
||||||
명시 — M0 전 완전 확정이 목표). Effect는 거의 수렴, 시그니처만 남음.
|
context-rejected.md`. 이 문서에는 **아직 완전히 열려있는 것 하나만** 남음
|
||||||
|
— 키 기반 동적 컬렉션 재조정. 사용자가 "작업 전에 모든 정의를 마치고
|
||||||
|
싶다"고 명시 — M0 전 완전 확정이 목표.
|
||||||
|
|
||||||
## 배경
|
## 배경
|
||||||
|
|
||||||
|
|
@ -25,20 +27,20 @@ Vide/v1/artworks 소스 근거 조사, Context 구현 난이도 판정) + 그
|
||||||
|
|
||||||
## 결론 요약
|
## 결론 요약
|
||||||
|
|
||||||
| 후보 | 판정 | 상태 |
|
| 후보 | 판정 | 현재 위치 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 키 기반 동적 컬렉션 재조정 | **진짜 빈 자리, 최우선** | 설계 진행 중 |
|
| 키 기반 동적 컬렉션 재조정 | **진짜 빈 자리, 최우선** — 아직 열려있음 | 이 문서(아래) |
|
||||||
| Effect(leaf 죽음에 확정 정리) | 진짜 빈 자리 | 거의 수렴, 시그니처만 남음 |
|
| Effect(leaf 죽음에 확정 정리) | 채택, 단 Observer와의 관계는 미해결 | `base/additional-primitives.md` |
|
||||||
| **Blocker(값 기반 emit 지연/합치기)** | **채택** — Batch의 대안 | 핵심 메커니즘+이름 확정, 문서화만 남음 |
|
| Blocker(값 기반 emit 지연/합치기) | **채택** — Batch의 대안 | `base/additional-primitives.md` |
|
||||||
| Batch(함수/코루틴 스코프 lexical block) | **기각** | 결정 완료 |
|
| Batch(함수/코루틴 스코프 lexical block) | **기각** | `archive/batch-rejected.md` |
|
||||||
| Context(트리 하위 암묵 전파) | **기각** — 대안(레이어드 Store)도 철회 | 결정 완료 |
|
| Context(트리 하위 암묵 전파) + 레이어드 Store | **기각** | `archive/context-rejected.md` |
|
||||||
| Observer에 cleanup 반환 계약 추가 | **기각** — 클로저 업밸류로 이미 충분 | 결정 완료 |
|
| Observer에 cleanup 반환 계약 추가 | **기각** — 클로저 업밸류로 이미 충분 | `base/additional-primitives.md`(Effect 절에 근거만 인용) |
|
||||||
| Untrack/Peek | 빈 자리 아님 | - |
|
| Untrack/Peek | 빈 자리 아님 | 아래 "빈 자리 아닌 것" 절 |
|
||||||
| Suspense/비동기 경계 | 빈 자리 아님(문서화 문제로 재분류) | - |
|
| Suspense/비동기 경계 | 빈 자리 아님(문서화 문제로 재분류) | 아래 "빈 자리 아닌 것" 절 |
|
||||||
| Error Boundary | 빈 자리 아님 | - |
|
| Error Boundary | 빈 자리 아님 | 아래 "빈 자리 아닌 것" 절 |
|
||||||
| Readonly wrapper | 빈 자리 아님 | - |
|
| Readonly wrapper | 빈 자리 아님 | 아래 "빈 자리 아닌 것" 절 |
|
||||||
|
|
||||||
## 1. 키 기반 동적 컬렉션 재조정 — 최우선, 설계 진행 중
|
## 키 기반 동적 컬렉션 재조정 — 최우선, 설계 진행 중
|
||||||
|
|
||||||
**무엇인가**: 데이터 배열(인벤토리, 리더보드, 채팅로그처럼 삽입/삭제/
|
**무엇인가**: 데이터 배열(인벤토리, 리더보드, 채팅로그처럼 삽입/삭제/
|
||||||
재정렬되는 목록)을 UI로 렌더링할 때, 이전 렌더 결과와 새 데이터를
|
재정렬되는 목록)을 UI로 렌더링할 때, 이전 렌더 결과와 새 데이터를
|
||||||
|
|
@ -157,231 +159,6 @@ Slot:Add(element, index?) -- 기존 그대로, Extract로 뺀 것도 다시
|
||||||
먼저 정하고 나중에 여기를 끼워맞추면 재작업이 날 가능성이 높음. Slot
|
먼저 정하고 나중에 여기를 끼워맞추면 재작업이 날 가능성이 높음. Slot
|
||||||
CRUD 시맨틱을 정의할 때 이 프리미티브의 요구사항을 같이 고려할 것.
|
CRUD 시맨틱을 정의할 때 이 프리미티브의 요구사항을 같이 고려할 것.
|
||||||
|
|
||||||
## 2. Effect — 거의 수렴, 시그니처만 남음
|
|
||||||
|
|
||||||
### Observer에 cleanup 반환 계약을 추가하는 안 — 기각
|
|
||||||
|
|
||||||
React `useEffect`류처럼 "`fn`이 `nil | () -> ()`를 반환하면 다음 재실행
|
|
||||||
직전에 그걸 불러준다"를 `state:Observer(fn)`에 얹는 안을 검토했으나
|
|
||||||
**사용자가 기각** — 클로저 업밸류로 이미 쉽게 되고 잘 작동하는데
|
|
||||||
(`local lastConn; state:Observer(function() if lastConn then
|
|
||||||
lastConn:Disconnect() end; lastConn = ... end)`), 프레임워크가 이걸
|
|
||||||
대신해줄 이유가 약하다는 판단. 이건 `pre-implementation-audit.md`
|
|
||||||
3-1번("`:Compute(fn)`의 `previous` 인자 — 클로저 업밸류로 이미 되는 걸
|
|
||||||
별도 API로 만든 것일 수 있음")과 **정확히 같은 논리** — 일관성 있는
|
|
||||||
판단으로 보임(3-1 자체도 같은 이유로 재검토 대상일 수 있음, 별도 항목).
|
|
||||||
|
|
||||||
### Effect — 별도의 단순한 primitive로, "leaf 죽음에 확정 정리"만 담당
|
|
||||||
|
|
||||||
Roblox엔 `task.spawn`으로 코루틴에 반복문/타이머를 돌리는 패턴이 흔하고,
|
|
||||||
Luau 테이블엔 `__gc` 같은 GC 시점 훅이 없어서 "이게 진짜 사라지는 순간"을
|
|
||||||
아는 유일한 방법은 `Instance.Destroying`류 명시적 신호뿐이다. 이런
|
|
||||||
케이스(타이머 시작 → leaf가 죽을 때 반드시 정지)를 위한 별도 primitive는
|
|
||||||
필요하다는 데 합의:
|
|
||||||
|
|
||||||
```
|
|
||||||
Effect(fn) -> EffectHandle -- fn을 즉시 1회 실행, 리턴값(nil | () -> ())은
|
|
||||||
-- 이 Effect가 바인드된 leaf가 죽을 때 정확히 1회 호출
|
|
||||||
```
|
|
||||||
|
|
||||||
Observer와 달리 **재실행 개념이 없다** — 값 변화에 반응해 다시 도는 건
|
|
||||||
Observer(+클로저로 직접 짠 cleanup)의 영역이고, Effect는 순수하게 "설치 +
|
|
||||||
확정 정리" 페어 하나만 담당한다. children 배열에 leaf로 놓는 기존 Observer
|
|
||||||
바인딩 패턴을 그대로 재사용(그 leaf가 살아있는 동안만 유효, leaf가 죽으면
|
|
||||||
정리 콜백 호출). 비용은 leaf당 실제 Destroying 바인딩 하나(공유 weak
|
|
||||||
table로 되는 Observer보다 비쌈) — 사용자가 필요할 때만 쓰는 걸로 충분.
|
|
||||||
|
|
||||||
**상태**: 사실상 수렴 — 시그니처(`fn`의 인자 유무, `EffectHandle`의 모양)만
|
|
||||||
다듬으면 `base/`로 승격 가능해 보임. 계속 논의 원하면 이어감.
|
|
||||||
|
|
||||||
## 3. Batch(함수/코루틴 스코프 lexical block) — 기각 확정
|
|
||||||
|
|
||||||
**중요**: 아래는 "값 기반 지연/합치기"라는 문제 자체를 기각한 게 아니라,
|
|
||||||
**그 문제를 lexical block(Solid `batch()`/MobX `runInAction()`류)으로
|
|
||||||
풀려는 접근만** 기각한 것이다. 실제 해법은 완전히 다른 별개 primitive인
|
|
||||||
**Blocker**(아래 3-1번)로 채택됨 — 이 절은 "왜 lexical 접근은 안 되는가"만
|
|
||||||
다루는 순수 반면교사 기록으로 남긴다.
|
|
||||||
|
|
||||||
### "즉시 pull"이 뭔지 (참고용 예시)
|
|
||||||
|
|
||||||
store-bind 핸들러(예: `Frame { BackgroundColor3 = total }`)는 lazy가
|
|
||||||
아니다 — 화면에 실제로 반영하는 "누군가"가 바로 이 핸들러 자신이라,
|
|
||||||
무효화 신호를 받자마자 스스로 `Get()`하고 바로 대입한다:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local total = a:With(b):Compute(function(av, bv) return av + bv end)
|
|
||||||
Frame { BackgroundColor3 = total }
|
|
||||||
|
|
||||||
a:Set(1) -- total 무효화 → 핸들러가 즉시 (av=1, bv=이전b)로 재계산+대입
|
|
||||||
b:Set(2) -- total 무효화 → 핸들러가 즉시 (av=1, bv=2)로 다시 재계산+대입
|
|
||||||
-- BackgroundColor3가 중간값을 한 번 거쳐가고, 두 번 대입됨
|
|
||||||
```
|
|
||||||
|
|
||||||
### 왜 lexical block 방식을 안 쓰는가
|
|
||||||
|
|
||||||
`Batch(fn)`을 "플래그 세우고 fn 실행, 끝나면 flush"로 구현하면 **fn이
|
|
||||||
yield하는 순간 위험해진다**(사용자 지적, 정확함):
|
|
||||||
|
|
||||||
1. 플래그가 전역이면, yield 중 스케줄러가 돌리는 무관한 코루틴의 `Set()`이
|
|
||||||
이 Batch에 잘못 휘말릴 수 있음.
|
|
||||||
2. 플래그를 코루틴 스코프로 만들어도(Fusion `Contextual`류 코루틴 키 weak
|
|
||||||
table), `fn` 안에서 새 코루틴을 스폰하는 API(Promise, `task.spawn`)를
|
|
||||||
부르면 그 새 코루틴은 배치 스코프를 상속 못 받아 일부 Set이 새어나감.
|
|
||||||
3. `fn`이 영원히 재개 안 되면(장시간 대기, 리크된 코루틴) flush가 영영 안
|
|
||||||
일어나 store-bind 핸들러들이 화면을 무기한 stale 상태로 방치 — 즉시
|
|
||||||
pull보다 더 나쁜 실패 모드.
|
|
||||||
|
|
||||||
이건 구현을 잘 짜서 피할 수 있는 버그가 아니라 **lexical block 모델
|
|
||||||
자체가 협조적 스케줄링 환경과 구조적으로 안 맞는 것**으로 판단, 기각.
|
|
||||||
|
|
||||||
## 3-1. Blocker — 채택된 새 primitive (Batch의 대안, 2026-08-06~07 세션)
|
|
||||||
|
|
||||||
### 핵심 아이디어 — 콜스택/코루틴이 아니라 값으로 지연 구간을 표현
|
|
||||||
|
|
||||||
Batch가 실패하는 근본 이유는 "지연 구간"을 콜스택/코루틴 스코프로
|
|
||||||
표현하려 했기 때문이다. `Blocker`는 그 구간을 **그냥 사용자가 들고 있는
|
|
||||||
값**으로 표현한다 — `On()`/`Off()`를 부르는 두 시점 사이에 얼마나 많은
|
|
||||||
yield/코루틴 전환이 끼어도, 심지어 완전히 다른 코루틴에서 `Off()`를
|
|
||||||
불러도 아무 문제가 없다. Batch를 무너뜨렸던 세 가지 실패 모드(전역 플래그
|
|
||||||
오염, 새 코루틴 미상속, 영구 yield로 인한 무기한 방치)가 구조적으로 전부
|
|
||||||
해당 안 됨.
|
|
||||||
|
|
||||||
### 메커니즘 (확정)
|
|
||||||
|
|
||||||
```
|
|
||||||
Blocker() -> blocker -- 생성자
|
|
||||||
blocker:On() -> self -- IsBlocked = true로만 설정, 그 외 아무것도 안 함
|
|
||||||
blocker:Off() -> self -- IsBlocked = false로 먼저 설정, 그 다음 등록된
|
|
||||||
-- onunblock 핸들 전부 실행(순서 무관, idempotent)
|
|
||||||
|
|
||||||
state:Block(blocker) -> state -- 새 gated state 반환. **호출되는 즉시**(나중에
|
|
||||||
-- 처음 블록될 때가 아니라) onunblock 핸들을
|
|
||||||
-- blocker의 weak 배열에 등록.
|
|
||||||
```
|
|
||||||
|
|
||||||
gated state의 동작:
|
|
||||||
- 원본 state가 emit(무효화)될 때, 이 gated state로 전파를 시도.
|
|
||||||
- `blocker.IsBlocked`이면: 전파 안 하고 `HasBlockedEmit = true`만 세팅.
|
|
||||||
- `blocker.IsBlocked`가 아니면: 평소처럼 그냥 전파(투명하게 통과).
|
|
||||||
- `blocker:Off()`가 실행하는 onunblock 핸들은: `HasBlockedEmit`을 확인해
|
|
||||||
true면 그제서야 정확히 1회 전파(emit)하고 플래그를 리셋. 이미 false면
|
|
||||||
아무 것도 안 함(이미 언블록 상태에서 다시 Off를 불러도 안전 —
|
|
||||||
idempotent).
|
|
||||||
|
|
||||||
**`:Get()`엔 영향 없음** — 블록은 emit **전파**(eager 소비자에게 "바뀌었다"
|
|
||||||
알리는 신호)만 지연시킨다. 블록 중이라도 누군가 명시적으로 `:Get()`하면
|
|
||||||
그 순간의 실제 값을 정상적으로 계산해서 준다 — `store-semantics.md`의
|
|
||||||
"Get()은 라이브 레퍼런스를 준다" 원칙과 일치.
|
|
||||||
|
|
||||||
### 사용 예시 — `state1, state2 -> state3` 케이스의 정답
|
|
||||||
|
|
||||||
처음 문제 제기("state1, state2 -> state3로 갈 때 둘 다 업데이트하면 state3가
|
|
||||||
두 번 계산됨, 한번에 할 방법이 없다")에 대한 답: **`state3`(결합된 결과)
|
|
||||||
하나에만 `:Block`을 걸면 된다** — `state1`/`state2` 각각에 걸 필요 없음:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local blocker = Blocker()
|
|
||||||
local gated3 = state3:Block(blocker) -- 소비자는 gated3를 구독
|
|
||||||
|
|
||||||
blocker:On()
|
|
||||||
state1:Set(1) -- state3 무효화 → gated3로 전파 시도 → 블록됨 → HasBlockedEmit=true
|
|
||||||
state2:Set(2) -- state3 무효화 → gated3로 전파 시도 → 이미 true, 그대로
|
|
||||||
blocker:Off() -- onunblock 핸들 실행 → HasBlockedEmit 확인 → 딱 한 번 emit
|
|
||||||
```
|
|
||||||
|
|
||||||
**일반 사용 가이드(확정, 문서화 필수)**: Block은 **파이프라인의 최종
|
|
||||||
연산 지점**(실제로 무거운 계산이 일어나는 derived state, eager 소비자에
|
|
||||||
가장 가까운 지점)에 거는 게 원칙 — 소스가 여러 개든, 하나가 한 주기에
|
|
||||||
여러 번 바뀌든 상관없이 이 지점 하나만 지키면 됨. 소스 쪽에 각각 거는 게
|
|
||||||
아니다.
|
|
||||||
|
|
||||||
### 이름 확정
|
|
||||||
|
|
||||||
- 클래스: `Blocker` — `Observer`/`Modifier`/`Ref`와 같은 명사-행위자
|
|
||||||
네이밍 관례와 일치.
|
|
||||||
- Blocker 자신의 토글: **`On()`/`Off()` -> self** (`Block()`/`Unblock()`
|
|
||||||
아님) — `state:Block(blocker)`가 이미 "배선(wiring)" 동작의 동사로
|
|
||||||
"Block"을 쓰고 있어서, Blocker 자신의 토글까지 같은 단어를 쓰면
|
|
||||||
`blocker:Block()`(블로커를 켠다)과 `state:Block(blocker)`(state를 이
|
|
||||||
블로커에 배선한다)가 같은 단어로 다른 두 동작을 가리키게 됨 — 자체
|
|
||||||
API 안에서 `register`→`State` 리네임 때 겪었던 "모호함은 풀었는데
|
|
||||||
충돌이 새로 생긴" 패턴이 반복될 뻔한 걸 미리 피함.
|
|
||||||
- 필드: **`IsBlocked`**(Blocker 자신의 On/Off 상태) — `Enabled`보다
|
|
||||||
명확(enabled는 "정상 작동 중"으로도 읽혀 헷갈릴 수 있음).
|
|
||||||
- 필드: **`HasBlockedEmit`**(gated state의 대기 플래그) — `Is`/`Has`
|
|
||||||
접두어로 불리언임을 바로 알려줌.
|
|
||||||
- 메소드: `state:Block(blocker) -> state`.
|
|
||||||
|
|
||||||
### 재진입(네스팅) — 의도적으로 미지원, 강한 문서화 필수
|
|
||||||
|
|
||||||
`IsBlocked`는 카운터가 아니라 단순 불리언이고, **의도적으로 그렇게
|
|
||||||
둔다.** 레퍼런스 카운팅으로 네스팅을 지원할 수도 있었지만, 그러면
|
|
||||||
"`On()` 여러 번, `Off()` 실수로 적게" 같은 버그가 **영구 블록으로 조용히
|
|
||||||
새는** 더 위험한 실패 모드를 만든다. 언어 차원에서 "On~Off 사이 코드가
|
|
||||||
죽었는지, 스레드가 죽었는지"를 추적하는 것도 해키해서 하지 않기로 함(Rust의
|
|
||||||
"poisoned mutex" — 락 구간 안에서 패닉이 나면 락이 오염 상태가 되는 것과
|
|
||||||
유사한 문제의식, 그 트래킹 자체를 만들지 않기로 함).
|
|
||||||
|
|
||||||
**대신 확정된 규칙**: 겹치는 배치가 필요하면 **각자 새 `Blocker` 인스턴스를
|
|
||||||
만들 것** — 하나의 Blocker를 여러 컨텍스트에서 재사용/중첩하지 않는다.
|
|
||||||
`Off()`는 스태킹 없이 즉시 그 자리에서 꺼진다. **이 제약은 반드시 사용자
|
|
||||||
문서(API 레퍼런스 수준)에 명시적으로 강조할 것** — 네스팅을 시도하면
|
|
||||||
조용히 잘못된 시점에 조기 해제되는, 원인 추적이 어려운 버그로 이어짐.
|
|
||||||
|
|
||||||
### 상태: 핵심 메커니즘+이름 확정. 남은 건 문서화뿐
|
|
||||||
|
|
||||||
`Extract`/`Add`처럼 세부 시그니처가 더 필요한 다른 항목들과 달리, Blocker는
|
|
||||||
설계 질문이 남아있지 않음 — `base/`로 승격 가능한 수준. quadnomicon에서
|
|
||||||
"Batch를 기각하고 왜 Blocker로 갔는가"를 비교 설명하는 게 좋은 소재(아래
|
|
||||||
"문서화 백로그" 참고).
|
|
||||||
|
|
||||||
## 4. Context — 기각 확정, 레이어드 Store 대안도 철회 (결정 완료)
|
|
||||||
|
|
||||||
### 난이도 판정 요약
|
|
||||||
|
|
||||||
서브에이전트 조사 결과: 동기적 저작 트리에 한정한 얕은 버전(Fusion
|
|
||||||
`Contextual`류 코루틴 키 weak table push-pop)은 구현 난이도 **낮음**이지만,
|
|
||||||
quad가 정상 패턴으로 확정한 "Slot에 이벤트 핸들러/코루틴에서 비동기로
|
|
||||||
자식이 추가되는 경우"엔 **에러 없이 조용히 기본값으로 폴백**하는 함정이
|
|
||||||
있음. 완전 자동(비동기 추가까지 자동 전파)은 Roblox Luau에
|
|
||||||
thread-local/async-context-propagation 훅이 없어 **사실상 불가**(플랫폼
|
|
||||||
한계, Node `AsyncLocalStorage`/Python `contextvars`도 "자동"이 아니라
|
|
||||||
"async 경계마다 명시적 캡처+재진입"으로 같은 문제를 품).
|
|
||||||
|
|
||||||
### 기각 이유
|
|
||||||
|
|
||||||
얕은 버전조차: (1) `base/slot-plan.md`가 정상 패턴으로 명시한 Slot 비동기
|
|
||||||
추가에서 가장 먼저, 가장 조용히 깨짐. (2) `research/debug-tooling-plan.md`의
|
|
||||||
"모든 연결은 선언된 그래프여야 한다"는 quad-debug 철학과 충돌하는 안 보이는
|
|
||||||
채널을 만듦.
|
|
||||||
|
|
||||||
### 레이어드 Store 대안도 철회 (사용자 반박 수용)
|
|
||||||
|
|
||||||
이전 라운드에서 대안으로 "레이어드 Store"(자식 Source 모음이 없는 키는
|
|
||||||
부모로 `__index` 폴백)를 권고했는데, 사용자 반박으로 철회함:
|
|
||||||
|
|
||||||
- quad는 이미 **타입으로 강제되는 명시적 Store 전달**을 갖고 있고, 이건
|
|
||||||
Context의 "Provider 안 넣으면 조용히 기본값/에러"보다 **더 안전**하다
|
|
||||||
(컴파일타임에 걸림 vs 런타임에 조용히 새는 값) — Context보다 나은 지점.
|
|
||||||
- "필드 일부만 오버라이드, 나머지는 부모 값 그대로"가 필요하면, 오버라이드
|
|
||||||
지점에서 `Store({...부모값, 변경필드=새값})`을 **한 번 명시적으로**
|
|
||||||
만들어서 그 지점부터 평소처럼 prop으로 넘기면 끝 — Modifier가 이미 쓰는
|
|
||||||
"merge, 나중 게 이김" 패턴 재사용 가능, 새 primitive 불필요. 레이어드
|
|
||||||
Store(읽는 시점에 몇 단계를 거슬러 올라가는지 안 보이는 자동 폴백)는
|
|
||||||
정확히 Context와 같은 이유(디버깅 어려움 — "이 값이 왜 이거지?"를
|
|
||||||
추적하려면 부모 체인을 다 훑어야 함)로 얻는 것보다 잃는 게 크다.
|
|
||||||
- 서드파티 라이브러리가 뭔가 필요하면, 타입이 강제하는 명시적 요구
|
|
||||||
(`props.Theme: Store<Theme>`)가 "몰래 안 줘서 죽는다"보다 나은 실패
|
|
||||||
모드 — Slot 기반 저작 모델(부모가 자손을 직접 구성)에서도 이런 요청은
|
|
||||||
자연스럽게 props로 흐름.
|
|
||||||
|
|
||||||
### 결론
|
|
||||||
|
|
||||||
Context, 레이어드 Store 둘 다 프리미티브로 만들지 않음. "왜 Context가
|
|
||||||
없는가"(명시적 Store 전달이 이미 그 역할을 하고, 타입 강제가 Context의
|
|
||||||
실패 모드보다 안전하다는 논증)도 quadnomicon 에세이 후보로 등록(아래
|
|
||||||
"문서화 백로그" 참고).
|
|
||||||
|
|
||||||
## 빈 자리 아닌 것으로 확인된 것들
|
## 빈 자리 아닌 것으로 확인된 것들
|
||||||
|
|
||||||
- **Untrack/Peek**(Solid `untrack()`, Vue `toRaw`): quad는 Vide식 암묵
|
- **Untrack/Peek**(Solid `untrack()`, Vue `toRaw`): quad는 Vide식 암묵
|
||||||
|
|
@ -411,9 +188,10 @@ Context, 레이어드 Store 둘 다 프리미티브로 만들지 않음. "왜 Co
|
||||||
## 문서화 백로그 (2026-08-06~07, `documentation-content-map.md`에도 반영)
|
## 문서화 백로그 (2026-08-06~07, `documentation-content-map.md`에도 반영)
|
||||||
|
|
||||||
- **quadnomicon 에세이**:
|
- **quadnomicon 에세이**:
|
||||||
- "왜 lexical Batch를 기각하고 대신 값 기반 Blocker를 택했는가" — 코루틴
|
- "왜 lexical Batch를 기각하고 대신 값 기반 Blocker를 택했는가" —
|
||||||
yield 위험 분석(Batch 절)과 Blocker의 설계(3-1절)를 나란히 비교.
|
`archive/batch-rejected.md`와 `base/additional-primitives.md`의
|
||||||
- "왜 Context가 없는가"(명시적 타입 강제 Store 전달이 이미 그 역할을 함).
|
Blocker 절을 나란히 비교.
|
||||||
|
- "왜 Context가 없는가" — `archive/context-rejected.md` 참고.
|
||||||
- "왜 push-invalidate/pull-recompute 설계가 laziness와 재계산 방지를
|
- "왜 push-invalidate/pull-recompute 설계가 laziness와 재계산 방지를
|
||||||
최우선 목표로 뒀는가" — Blocker 같은 파생 프리미티브가 이 목표 위에서
|
최우선 목표로 뒀는가" — Blocker 같은 파생 프리미티브가 이 목표 위에서
|
||||||
자연스럽게 나온 이유까지 포함해 기존 심화 콘텐츠 후보 3번(`왜
|
자연스럽게 나온 이유까지 포함해 기존 심화 콘텐츠 후보 3번(`왜
|
||||||
|
|
@ -426,22 +204,12 @@ Context, 레이어드 Store 둘 다 프리미티브로 만들지 않음. "왜 Co
|
||||||
유연한 구조" — 명시적 의존성 선언(`:With`) 위에서도 실제 계산은
|
유연한 구조" — 명시적 의존성 선언(`:With`) 위에서도 실제 계산은
|
||||||
조건부로 일부만 쓸 수 있다는 팁.
|
조건부로 일부만 쓸 수 있다는 팁.
|
||||||
- "Blocker 사용 가이드" — 파이프라인 최종 연산 지점에 배치, **네스팅
|
- "Blocker 사용 가이드" — 파이프라인 최종 연산 지점에 배치, **네스팅
|
||||||
금지를 강하게 명시**(위 3-1절 "재진입" 참고, 문서화 시 최우선 강조
|
금지를 강하게 명시**(`base/additional-primitives.md`의 "재진입" 절
|
||||||
항목).
|
참고, 문서화 시 최우선 강조 항목).
|
||||||
- "여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산을 피하는
|
- "여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산을 피하는
|
||||||
파이프라인/업데이트 순서 팁"(Blocker를 안 쓰는 단순 케이스용 보조 팁,
|
파이프라인/업데이트 순서 팁"(Blocker를 안 쓰는 단순 케이스용 보조 팁,
|
||||||
기존 "심화 최적화 팁" 항목을 Blocker 존재를 전제로 재조정).
|
기존 "심화 최적화 팁" 항목을 Blocker 존재를 전제로 재조정).
|
||||||
|
|
||||||
## 제안 우선순위
|
|
||||||
|
|
||||||
1. **키 기반 컬렉션 재조정** — 여전히 최우선, 설계 진행 중. M0 이전 완전
|
|
||||||
확정이 목표.
|
|
||||||
2. **Effect** — 거의 수렴, 시그니처만 다듬으면 됨.
|
|
||||||
3. **Blocker** — 핵심 메커니즘+이름 확정, `base/`로 승격 가능한 수준.
|
|
||||||
문서화(특히 네스팅 금지 강조)만 남음.
|
|
||||||
4. **Batch(lexical)/Context** — 둘 다 결정 완료(기각), 더 이상 검토 대상
|
|
||||||
아님.
|
|
||||||
|
|
||||||
## 참고: 조사에 사용한 소스 근거
|
## 참고: 조사에 사용한 소스 근거
|
||||||
|
|
||||||
- Fusion: `State/ForPairs.luau`, `State/ForKeys.luau`,
|
- Fusion: `State/ForPairs.luau`, `State/ForKeys.luau`,
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,7 @@ UI를 클릭한 순간)에 강제로 그 계산을 트리거하는 건 이 전
|
||||||
**네이밍 컨벤션(사용자 제안)**: 내부 자동 생성 helper Instance는 `_`나
|
**네이밍 컨벤션(사용자 제안)**: 내부 자동 생성 helper Instance는 `_`나
|
||||||
`QUAD_` 같은 접두어를 붙여서 Explorer에서 직접 봤을 때 헷갈리지 않게 함 —
|
`QUAD_` 같은 접두어를 붙여서 Explorer에서 직접 봤을 때 헷갈리지 않게 함 —
|
||||||
이름 바꾸는 건 비용이 크지 않음. v1이 이미 `_quad_round`/`_quad_padding`/
|
이름 바꾸는 건 비용이 크지 않음. v1이 이미 `_quad_round`/`_quad_padding`/
|
||||||
`_quad_scale` 네이밍(`research/ui-shorthand-plan.md` 참고)으로 정확히
|
`_quad_scale` 네이밍(`base/ui-shorthand-plan.md` 참고)으로 정확히
|
||||||
이 관습을 썼던 전례 — quad-v2에서 내부 자동 생성물이 생기면 그대로
|
이 관습을 썼던 전례 — quad-v2에서 내부 자동 생성물이 생기면 그대로
|
||||||
재사용. `research/documentation-plan.md`의 "UI 네이밍 컨벤션 문서"
|
재사용. `research/documentation-plan.md`의 "UI 네이밍 컨벤션 문서"
|
||||||
백로그에도 이 구체적 규칙을 추가해둠.
|
백로그에도 이 구체적 규칙을 추가해둠.
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
11. **Ref 기초** — 외부 관리 Instance 참조/마이그레이션용, `CreatedRef(fn)` + 배열 위치로 자식 전/후 표현, "프로퍼티보다도 먼저" 필요할 때만 `PreRef`(2026-08-07 세 번째 세션, `phase` 옵션 폐기) (`architecture.md`, `bind-system-plan.md`)
|
11. **Ref 기초** — 외부 관리 Instance 참조/마이그레이션용, `CreatedRef(fn)` + 배열 위치로 자식 전/후 표현, "프로퍼티보다도 먼저" 필요할 때만 `PreRef`(2026-08-07 세 번째 세션, `phase` 옵션 폐기) (`architecture.md`, `bind-system-plan.md`)
|
||||||
12. **파생값 최소 예시** — `:With(...)` + `:Compute(fn)` 기본형 (`bind-system-plan.md`, `store-semantics.md`)
|
12. **파생값 최소 예시** — `:With(...)` + `:Compute(fn)` 기본형 (`bind-system-plan.md`, `store-semantics.md`)
|
||||||
13. **Tween 기초** — `[Tween(key, ...)] = storeValue`, 취소 시 현재 보간값에서 자연스럽게 이어짐 (`research/tween-plan.md`)
|
13. **Tween 기초** — `[Tween(key, ...)] = storeValue`, 취소 시 현재 보간값에서 자연스럽게 이어짐 (`research/tween-plan.md`)
|
||||||
14. **UI 숏핸드(quad-roblox 한정)** — `Corner`/`PaddingAllOffset`/`Scale` 인라인 키 (`research/ui-shorthand-plan.md`)
|
14. **UI 숏핸드(quad-roblox 한정)** — `UICorner`/`UIPadding`/`UIPaddingOffset`/`UIScale` 인라인 키 (`base/ui-shorthand-plan.md`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -135,7 +135,7 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부
|
||||||
실제 일어나는 derived state)에 배치하는 게 원칙이라는 것, **네스팅
|
실제 일어나는 derived state)에 배치하는 게 원칙이라는 것, **네스팅
|
||||||
금지를 최우선으로 강조**(겹치는 배치는 각자 새 `Blocker`를 만들 것 —
|
금지를 최우선으로 강조**(겹치는 배치는 각자 새 `Blocker`를 만들 것 —
|
||||||
안 지키면 조용히 잘못된 시점에 조기 해제되는 원인 추적 어려운 버그로
|
안 지키면 조용히 잘못된 시점에 조기 해제되는 원인 추적 어려운 버그로
|
||||||
이어짐) — `research/additional-primitives-plan.md` 3-1절
|
이어짐) — `base/additional-primitives.md`의 "Blocker" 절
|
||||||
19. 여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산/재대입을
|
19. 여러 Source를 한꺼번에 바꿀 때 Blocker 없이도 중복 재계산/재대입을
|
||||||
피하는 파이프라인/업데이트 순서 팁(Blocker를 안 쓰는 단순 케이스용
|
피하는 파이프라인/업데이트 순서 팁(Blocker를 안 쓰는 단순 케이스용
|
||||||
보조 팁) — `research/additional-primitives-plan.md` "문서화 백로그" 절
|
보조 팁) — `research/additional-primitives-plan.md` "문서화 백로그" 절
|
||||||
|
|
@ -197,13 +197,15 @@ additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**:
|
||||||
## 5. 문서화 아직 보류(미확정 설계라 쓰면 안 됨)
|
## 5. 문서화 아직 보류(미확정 설계라 쓰면 안 됨)
|
||||||
|
|
||||||
- Slot 형제 순서 보장 (`slot-plan.md`)
|
- Slot 형제 순서 보장 (`slot-plan.md`)
|
||||||
- Tween 오버라이드/삭제후재시작/끝점이동 세부 옵션 키 이름 (`research/tween-plan.md`)
|
- Tween 오버라이드/삭제후재시작/끝점이동 세부 옵션 키 이름, 트윈 옵션 값
|
||||||
- UI 숏핸드 `RoundSize` 드롭 여부 (`research/ui-shorthand-plan.md`)
|
모양(TweenInfo vs 편의 필드) (`research/tween-plan.md`)
|
||||||
- `Attribute<T>` 제네릭 vs 타입별 정적 생성자 (`bind-system-plan.md`)
|
- `Attribute<T>` 제네릭 vs 타입별 정적 생성자 (`bind-system-plan.md`)
|
||||||
- provider/processor 네이밍 (`module-lifecycle-plan.md`)
|
- provider/processor 네이밍 (`module-lifecycle-plan.md`)
|
||||||
- 키 기반 동적 컬렉션 재조정 최종 이름/시그니처(`Render`/`Draw`/`List` 등
|
- 키 기반 동적 컬렉션 재조정 최종 이름/시그니처(`Render`/`Draw`/`List` 등
|
||||||
후보만 있음, `Slot:Extract` 세부 시맨틱도 미정) — `research/
|
후보만 있음, `Slot:Extract` 세부 시맨틱도 미정) — `research/
|
||||||
additional-primitives-plan.md`(2026-08-06 신설, 설계 진행 중)
|
additional-primitives-plan.md`(2026-08-06 신설, 설계 진행 중)
|
||||||
|
- Effect가 `state:Effect()`로 Observer를 확장하는 형태인지, 완전히 독립된
|
||||||
|
free function인지 (`base/additional-primitives.md`의 "미해결" 절)
|
||||||
|
|
||||||
이 항목들은 `.claude/question.md`에도 이미 열린 질문으로 잡혀있음 — 여기선
|
이 항목들은 `.claude/question.md`에도 이미 열린 질문으로 잡혀있음 — 여기선
|
||||||
"확정 전엔 문서화 대상 아님"이라는 표시만 겸함.
|
"확정 전엔 문서화 대상 아님"이라는 표시만 겸함.
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ Roblox가 Play 중 라이브 UI 편집 도구를 꺼버려서 실제 화면의 U
|
||||||
강제할까(과한 선택지, 참고만)?
|
강제할까(과한 선택지, 참고만)?
|
||||||
|
|
||||||
**구체적으로 결정된 하위 규칙 하나(2026-08-06)**: quad가 내부적으로
|
**구체적으로 결정된 하위 규칙 하나(2026-08-06)**: quad가 내부적으로
|
||||||
자동 생성하는 helper Instance(예: `research/ui-shorthand-plan.md`의
|
자동 생성하는 helper Instance(예: `base/ui-shorthand-plan.md`의
|
||||||
UICorner/UIPadding/UIScale 숏핸드가 만드는 자식)는 `_`나 `QUAD_` 같은
|
UICorner/UIPadding/UIScale 숏핸드가 만드는 자식)는 `_`나 `QUAD_` 같은
|
||||||
접두어를 붙여 Explorer에서 직접 볼 때 사용자가 만든 것과 헷갈리지 않게
|
접두어를 붙여 Explorer에서 직접 볼 때 사용자가 만든 것과 헷갈리지 않게
|
||||||
함 — v1의 `_quad_round`류 네이밍 재사용(`research/debug-tooling-plan.md`
|
함 — v1의 `_quad_round`류 네이밍 재사용(`research/debug-tooling-plan.md`
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,7 @@ M11 착수 시.
|
||||||
|
|
||||||
### 2-11. UI shorthand — 기존 UICorner 자식과의 매칭 기준(이름 vs 타입)이 불명, 사용자 실수 유발 위험 높음
|
### 2-11. UI shorthand — 기존 UICorner 자식과의 매칭 기준(이름 vs 타입)이 불명, 사용자 실수 유발 위험 높음
|
||||||
|
|
||||||
**위치**: `research/ui-shorthand-plan.md` "v1 실제 메커니즘" 절.
|
**위치**: `base/ui-shorthand-plan.md` "v1 실제 메커니즘" 절.
|
||||||
|
|
||||||
**문제**: v1 메커니즘은 "기존 `UICorner` 자식이 있으면 재사용, 없으면
|
**문제**: v1 메커니즘은 "기존 `UICorner` 자식이 있으면 재사용, 없으면
|
||||||
`Instance.new("UICorner", item)`(`Name = "_quad_round"`)"라고 서술되는데,
|
`Instance.new("UICorner", item)`(`Name = "_quad_round"`)"라고 서술되는데,
|
||||||
|
|
@ -485,7 +485,7 @@ M11 착수 시.
|
||||||
|
|
||||||
### 3-2. Corner/PaddingAll/Scale 개별 Handler 대신 데이터 테이블 구동 단일 제네릭 Handler
|
### 3-2. Corner/PaddingAll/Scale 개별 Handler 대신 데이터 테이블 구동 단일 제네릭 Handler
|
||||||
|
|
||||||
**위치**: `research/ui-shorthand-plan.md` "메커니즘 — 새 아키텍처 개념
|
**위치**: `base/ui-shorthand-plan.md` "메커니즘 — 새 아키텍처 개념
|
||||||
불필요" 절.
|
불필요" 절.
|
||||||
|
|
||||||
**문제**: 문서는 "Corner/PaddingAll/Scale 같은 특수 키를 인식하는
|
**문제**: 문서는 "Corner/PaddingAll/Scale 같은 특수 키를 인식하는
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,16 @@
|
||||||
|
|
||||||
**상태**: research — 방향은 뚜렷하게 잡혀 있고(라이브러리가 트윈을 직접
|
**상태**: research — 방향은 뚜렷하게 잡혀 있고(라이브러리가 트윈을 직접
|
||||||
구현하지 않는다), `retract` 순서/오버라이드 기본값(Cancel)도 확정됨. 남은 건
|
구현하지 않는다), `retract` 순서/오버라이드 기본값(Cancel)도 확정됨. 남은 건
|
||||||
기본값 외 나머지 오버라이드 동작을 고르는 옵션 키의 정확한 이름/시그니처
|
기본값 외 나머지 오버라이드 동작을 고르는 옵션 키의 정확한 이름/시그니처와,
|
||||||
정도. 원본:
|
트윈 옵션을 어떤 값 모양으로 받을지(아래 "트윈 옵션 값 모양" 절, 신규)
|
||||||
|
정도. **중요 — 놓치기 쉬운 포인트**: `retract`는 Destroy(완전 소멸) 시엔
|
||||||
|
호출되지 않는다(아래 "`retract`는 완전 소멸 시엔 호출되지 않는다" 절) —
|
||||||
|
Tween 오버라이드 로직을 짤 때 "인스턴스가 파괴될 때도 이 코드가 실행될
|
||||||
|
것"이라고 가정하면 틀림. 원본:
|
||||||
`.claude/initreq/raw-userinput.md` "트윈은 어떻게 할 것이냐" / "스토어 값은
|
`.claude/initreq/raw-userinput.md` "트윈은 어떻게 할 것이냐" / "스토어 값은
|
||||||
항상 먼저 캐치한다" / "네임스페이스드 객체" 절. Fusion의 Tween/Spring이
|
항상 먼저 캐치한다" / "네임스페이스드 객체" 절. Fusion의 Tween/Spring이
|
||||||
반응 그래프 안에 있는 설계는 명시적 반면교사 — [정정: 절 제목이 실제와
|
반응 그래프 안에 있는 설계는 명시적 반면교사 — [정정: 절 제목이 실제와
|
||||||
달랐음] `base/comparison-fusion-vide.md`의 "Fusion" 절 마지막 불릿
|
달랐음] `reference/comparison-fusion-vide.md`의 "Fusion" 절 마지막 불릿
|
||||||
("Tween/Spring이 State그래프 안의 1급 노드") 참고.
|
("Tween/Spring이 State그래프 안의 1급 노드") 참고.
|
||||||
|
|
||||||
## 확정된 방향: 트윈을 Store/반응 그래프 밖에 둔다
|
## 확정된 방향: 트윈을 Store/반응 그래프 밖에 둔다
|
||||||
|
|
@ -79,6 +83,44 @@ Ref나 네임스페이스드 조회가 필요하지 않음. Tween의 store-bind
|
||||||
Destroy 시점엔 아무 것도 안 함(라이프타임 `Connected` 체크로 처리 자체를
|
Destroy 시점엔 아무 것도 안 함(라이프타임 `Connected` 체크로 처리 자체를
|
||||||
멈추는 것만으로 충분).**
|
멈추는 것만으로 충분).**
|
||||||
|
|
||||||
|
**메모 — `retract`와 `canExecute`는 서로 다른 문제를 다룬다, 나중에 quadnomicon
|
||||||
|
급에서 제대로 설명 필요.** "그럼 값 교체가 아니라 값을 계속 관측하는 쪽
|
||||||
|
(예: `state:Observer(fn)`으로 Tween을 건 경우)은 Destroy 시 어떻게
|
||||||
|
정리되는가?"라는 질문이 자연스럽게 따라오는데, 이건 `retract`의 영역이
|
||||||
|
아니라 `canExecute`(라이프타임 predicate, `base/lifecycle-pattern.md`의
|
||||||
|
"생명 바인드 유틸" 절)의 영역이다 — Destroy되면 `retract` 호출 없이 그냥
|
||||||
|
`canExecute`가 false가 되어 이후 처리 시도 자체가 조용히 no-op된다.
|
||||||
|
store-bind 일반(Tween 포함)도 같은 결이라 실제로는 이미 일관되게 명시돼
|
||||||
|
있지만(`base/bind-system-plan.md` "확정된 디스패치 모델" 절), "왜 이
|
||||||
|
경로엔 retract를 쓰고 저 경로엔 canExecute를 쓰는가"라는 내부 구조상의
|
||||||
|
이유는 quadnomicon 콘텐츠로 풀어서 설명할 필요가 있음(`research/
|
||||||
|
documentation-content-map.md` 심화 콘텐츠 후보에 메모) — 지금은 이 메모만
|
||||||
|
남겨두고 상세 설명은 나중 문서화 단계로 미룸.
|
||||||
|
|
||||||
|
## 트윈 옵션 값 모양 — TweenInfo 그대로 vs 편의 필드 (신규, 열린 논의)
|
||||||
|
|
||||||
|
**아직 논의 시작 단계 — 나중에 더 다룰 주제로만 남겨둠.** Roblox의
|
||||||
|
`TweenInfo.new(time, easingStyle, easingDirection, repeatCount, reverses,
|
||||||
|
delayTime)`는 순수 포지셔널 생성자인데, Luau엔 named call 문법이 없어서
|
||||||
|
직접 쓰면 `TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)`
|
||||||
|
처럼 각 인자가 뭘 뜻하는지 호출부만 보고 알기 어렵다. 후보:
|
||||||
|
|
||||||
|
1. **`TweenInfo`를 그대로 받는다** — 사용자가 이미 만들어둔 `TweenInfo`를
|
||||||
|
재사용하고 싶은 경우엔 상관없지만, 대부분의 흔한 케이스(길이/이징만
|
||||||
|
바꾸고 싶음)에서 매번 포지셔널 생성자를 마주해야 함.
|
||||||
|
2. **편의 필드로 개별 인자를 받고 기본값을 제공** — 예:
|
||||||
|
`{Time=0.3, Style=Enum.EasingStyle.Quad, Reverses=false, ...}`처럼
|
||||||
|
이름 붙은 키로 받고 흔한 기본값(예: `Time=0.2`, `Style=Quad`,
|
||||||
|
`Direction=Out`)을 채워줌. 명시적으로 `TweenInfo`가 이미 있어서
|
||||||
|
재사용하고 싶다는 케이스에도 열어두면(예: `Info = someTweenInfo`
|
||||||
|
필드로), 둘 다 지원 가능.
|
||||||
|
|
||||||
|
**현재 소견(확정 아님)**: 2번(편의 필드 + 기본값)이 흔한 사용 경험상 더
|
||||||
|
낫다는 쪽으로 기움 — 대부분의 호출에서 named call이 없는 `TweenInfo.new`의
|
||||||
|
가독성 문제를 피할 수 있고, 기본값 덕에 짧은 호출도 가능해짐. 다만
|
||||||
|
구체적인 필드 이름/기본값/`TweenInfo` 재사용 경로의 정확한 문법은 아직
|
||||||
|
확정 아님 — 나중 논의 대상으로 남김.
|
||||||
|
|
||||||
## 네임스페이스드 객체 (성능상 이유로 보류)
|
## 네임스페이스드 객체 (성능상 이유로 보류)
|
||||||
|
|
||||||
트윈 대상을 이름으로 찾는 별도 네임스페이스는 성능상 별로라고 판단 —
|
트윈 대상을 이름으로 찾는 별도 네임스페이스는 성능상 별로라고 판단 —
|
||||||
|
|
@ -92,3 +134,7 @@ CollectionService를 쓰는 게 나아 보이지만, 트윈 전용 네임스페
|
||||||
- 기본값(Cancel)은 확정됨. 남은 건 나머지 세 동작(오버라이드/삭제 후 재시작/
|
- 기본값(Cancel)은 확정됨. 남은 건 나머지 세 동작(오버라이드/삭제 후 재시작/
|
||||||
끝점 이동 후 재시작)을 선택하는 옵션 키의 정확한 이름/시그니처 — 구현
|
끝점 이동 후 재시작)을 선택하는 옵션 키의 정확한 이름/시그니처 — 구현
|
||||||
단계에서 확정.
|
단계에서 확정.
|
||||||
|
- 트윈 옵션 값 모양(위 "트윈 옵션 값 모양" 절, 신규) — `TweenInfo` 그대로
|
||||||
|
받을지 편의 필드+기본값으로 받을지, 소견은 후자 쪽이지만 확정 아님.
|
||||||
|
- 자연 완료(Completed) 시 per-instance 북키핑 정리 여부 —
|
||||||
|
`research/pre-implementation-audit.md` 2-10번 참고, M11 착수 시 확정.
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
# UI 편의 숏핸드 (Corner/Padding/Scale 등) — 인라인 적용 계획
|
|
||||||
|
|
||||||
**상태**: research — 2026-08-06 세션에서 결론까지 남. `Corner`/
|
|
||||||
`PaddingAll`/`Scale` 숏핸드 자체는 **여전히 필요**(사용자 재확정, 아래
|
|
||||||
"결론" 절 — 이전에 이 문서가 한 차례 "포팅 불필요"로 잘못 정리했던 걸
|
|
||||||
정정함). 패키지 배치는 `quad-roblox` 코어 직접 포함으로 확정.
|
|
||||||
|
|
||||||
## 배경
|
|
||||||
|
|
||||||
사용자 기억: v1을 쓸 때 "UICorner/UIPadding/UIScale 같은 걸 직접
|
|
||||||
`Instance.new`로 만들어 Parent하는 귀찮은 작업 없이, Frame 안에 인라인으로
|
|
||||||
넣기만 해도 CSS 스타일처럼 적용됐다 — 코드가 줄고 읽기도 편해서 꽤
|
|
||||||
괜찮았다"는 것. 문서 어디에도 기록된 적 없어 v1 소스(`.claude/initreq/quad`)와
|
|
||||||
PA님 코드(`.claude/initreq/artworks`)를 서브에이전트로 조사.
|
|
||||||
|
|
||||||
## v1 실제 메커니즘 (조사 완료)
|
|
||||||
|
|
||||||
`class.lua`의 `SetProperty`/`GetProperty`(38~109행)와 `ProcessQuadProperty`
|
|
||||||
(134~213행)에 하드코딩된 if/elseif 분기로 특수 문자열 키 5종을 지원:
|
|
||||||
|
|
||||||
- `RoundSize = 16` → `ImageLabel`/`ImageButton` 전용, UICorner가 아니라
|
|
||||||
이미지 자체의 9-slice 라운드 처리(`round.SetRound()`) — **UICorner 계열과
|
|
||||||
메커니즘이 다름**.
|
|
||||||
- `Corner = 8` → 숫자 하나. 기존 `UICorner` 자식이 있으면 재사용, 없으면
|
|
||||||
`Instance.new("UICorner", item)`으로 생성(`Name = "_quad_round"`),
|
|
||||||
`CornerRadius = UDim.new(0, value)` 설정.
|
|
||||||
- `PaddingAll = UDim.new(...)` / `PaddingAllOffset = 50` → 동일 패턴,
|
|
||||||
`UIPadding`(`_quad_padding`).
|
|
||||||
- `Scale = 1.2` → 동일 패턴, `UIScale`(`_quad_scale`).
|
|
||||||
|
|
||||||
값 모양은 항상 **리터럴 하나**(숫자/UDim) — 테이블도 `__type` 태그도 아님.
|
|
||||||
실사용 예시(`md/kr/tutorial/7_quadProperty.md`):
|
|
||||||
```lua
|
|
||||||
Frame "mainFrame" {
|
|
||||||
PaddingAllOffset = 50;
|
|
||||||
ImageFrame { RoundSize = 16; ... };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`UIListLayout`/`UIGridLayout`/flex는 이런 전용 숏핸드가 v1에 없었음** —
|
|
||||||
`ProcessQuadProperty`의 범용 자식 나열 분기(배열 인덱스로 놓인 Instance/
|
|
||||||
Class 결과를 자동 mount, 207~213행)로 `UIListLayout{...}`을 그냥 직접
|
|
||||||
나열했을 뿐, `List = true` 같은 전용 축약 문법은 레포 전체(PA님 코드
|
|
||||||
포함)에서 찾지 못함. **quad-v2도 이 부분은 이미 있는 children-array +
|
|
||||||
인스턴스 생성 문법으로 그대로 커버됨 — 새로 설계할 것 없음.** 사용자
|
|
||||||
기억 중 이 부분은 "전용 숏핸드"가 아니라 "선언형 문법 자체가 원래
|
|
||||||
간결하다"는 것과 섞였을 가능성이 큼.
|
|
||||||
|
|
||||||
## 결론 (2026-08-06, 한 차례 오해 후 재정정)
|
|
||||||
|
|
||||||
**RoundSize와 Corner는 서로 다른 이유로 존재했던 별개 기능 — 혼동하지
|
|
||||||
말 것**:
|
|
||||||
- **`RoundSize`(이미지 9-slice 라운드)**: `ImageLabel`/`Button`을
|
|
||||||
이미지 트릭으로 둥글게 보이게 하던 것 — **당시 Roblox에 `UICorner` 같은
|
|
||||||
네이티브 구현체가 없었기 때문에** 존재하던 워크어라운드. 지금은
|
|
||||||
`UICorner`가 안정적인 네이티브 Instance라 이 이미지 트릭 자체를 그대로
|
|
||||||
포팅할 이유는 없음(이미지에도 그냥 실제 `UICorner`를 쓰면 됨) —
|
|
||||||
**RoundSize는 포팅 안 함**.
|
|
||||||
- **`Corner`/`PaddingAll`/`Scale`(UICorner/UIPadding/UIScale 자동
|
|
||||||
생성)**: 이건 워크어라운드가 아니라 **지금도 유효한 편의 기능** —
|
|
||||||
**사용자 재확정**: "UIScale 같은 건 여전히 별도의 Instance고 부모
|
|
||||||
Frame에 영향을 주는 구조, 숏핸드는 여전히 필요하다". `UICorner`가
|
|
||||||
네이티브가 됐다고 해서 "별도 Instance를 만들어 부모에 Parent해야
|
|
||||||
한다"는 구조적 번거로움 자체가 없어지는 게 아니므로, 이 숏핸드의
|
|
||||||
존재 이유는 여전히 유효함 — **이전 정리("포팅 불필요")는 오해였고
|
|
||||||
정정함, `Corner`/`PaddingAll`/`Scale`은 그대로 포팅 대상.**
|
|
||||||
|
|
||||||
**메커니즘 — 새 아키텍처 개념 불필요**: 이미 있는 pluggable Handler로
|
|
||||||
그대로 커버됨. `Corner`/`PaddingAll`/`Scale` 같은 특수 키를 인식하는
|
|
||||||
Handler(`isHandlable`이 그 키를 매칭)가 "이름 붙은 자식을 찾거나 만들고
|
|
||||||
프로퍼티 세팅"을 `process(inst, k, v)`에 구현 — v1의 하드코딩 if/elseif
|
|
||||||
대신 정식 핸들러 계약(`isHandlable`/`priority`/`process`/`retract`)을
|
|
||||||
따르는 것만 다름. `modifier-plan.md`가 이미 예시로 든
|
|
||||||
`Modifier.Rounded(8)`은 이 특수 키를 flatten해서 props에 꽂아넣는 사탕
|
|
||||||
문법일 뿐, 실제 처리는 이 Handler가 함 — Modifier를 안 거치고
|
|
||||||
`Frame { Corner = 8 }`처럼 순수 인라인 키로 직접 써도(v1처럼) 동일하게
|
|
||||||
작동함, `architecture.md`의 `[Attribute "Name"]`류 특수 키와 같은 층위.
|
|
||||||
자동 생성된 자식은 위 "핵심 설계 방향" 관례대로 `_`/`QUAD_` 접두어
|
|
||||||
네이밍(`research/debug-tooling-plan.md` 9번, v1의 `_quad_round`류
|
|
||||||
그대로 재사용).
|
|
||||||
|
|
||||||
**패키지 배치 — `quad-roblox` 코어에 직접 포함, 확정**: "트윈도 인스턴스
|
|
||||||
생성/제어를 직접 구현 가능한 걸 하나로 묶어 쉽게 쓰게 합친 것 — 너무
|
|
||||||
잘게 쪼개 오버엔지니어링하기보다 확실히 하나로 코어에 넣어도 충분하다,
|
|
||||||
opt-out할 이유가 별로 없다"는 게 사용자 판단 — **작고 항상 켜져 있어도
|
|
||||||
비용이 무시할 만한 편의 기능은 별도 opt-out 패키지로 쪼개지 말고
|
|
||||||
`quad-roblox` 코어에 직접 포함한다**는 원칙으로 확정(이미 계획된 Tween
|
|
||||||
핸들러가 같은 모양이라는 게 근거). 이 원칙은 일반화해서 재사용 가능 —
|
|
||||||
앞으로 비슷한 "작은 인스턴스 편의 기능"이 제안되면 `quad-roblox-util`
|
|
||||||
같은 걸 새로 만들지 않고 이 선례를 따르면 됨.
|
|
||||||
|
|
||||||
**중요도**: 낮음("이건 나중에도 쉽게 구현됨" — 사용자) — 지금 M0 우선순위를
|
|
||||||
바꿀 이유는 없음, M10(Handlers/Attribute 등) 전후로 다른 세부 Handler와
|
|
||||||
함께 구현하면 충분.
|
|
||||||
|
|
||||||
## 열린 질문 (`.claude/question.md`에도 취합)
|
|
||||||
|
|
||||||
- 이름 그대로 가져올지(`Corner`/`PaddingAll`/`PaddingAllOffset`/`Scale`)
|
|
||||||
재검토할지 — 진행 중인 용어 정리(`CLAUDE.md` "지금 할 일" 2번)에 합류
|
|
||||||
대상.
|
|
||||||
- `RoundSize`(이미지 라운드)를 완전히 드롭할지, 아니면 이미지 대상에도
|
|
||||||
그냥 실제 `UICorner`를 자동 적용하는 것으로 대체할지 — 후순위.
|
|
||||||
|
|
@ -61,7 +61,7 @@ v1(`.claude/initreq/quad/src`) 조사 결과, API는 성격이 다른 두 계층
|
||||||
핸들러 분기도 안 생김. 단, "quad-debug 추적 밖 mutate 경로가 생긴다"는
|
핸들러 분기도 안 생김. 단, "quad-debug 추적 밖 mutate 경로가 생긴다"는
|
||||||
근거(4번)는 격리해도 남는 문제라 별도 검토 필요.
|
근거(4번)는 격리해도 남는 문제라 별도 검토 필요.
|
||||||
- **RoundSize 등 특수 키**: `Corner`/`PaddingAll`/`Scale`은 이미
|
- **RoundSize 등 특수 키**: `Corner`/`PaddingAll`/`Scale`은 이미
|
||||||
`research/ui-shorthand-plan.md`에서 네이티브 포팅 확정됨 — 별도 compat
|
`base/ui-shorthand-plan.md`에서 네이티브 포팅 확정됨 — 별도 compat
|
||||||
작업 불필요. `RoundSize`(이미지 9-slice 라운드 트릭)만 UICorner 없던
|
작업 불필요. `RoundSize`(이미지 9-slice 라운드 트릭)만 UICorner 없던
|
||||||
시절 워크어라운드라 재현 자체가 불필요하다고 이미 결론남.
|
시절 워크어라운드라 재현 자체가 불필요하다고 이미 결론남.
|
||||||
- **`target()`/Linker**: 위 정정대로 "이름 있는 자식 등록 + 시그널 중계"일
|
- **`target()`/Linker**: 위 정정대로 "이름 있는 자식 등록 + 시그널 중계"일
|
||||||
|
|
|
||||||
76
CLAUDE.md
76
CLAUDE.md
|
|
@ -38,10 +38,14 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
|
||||||
`.claude/README.md`가 색인. 요약:
|
`.claude/README.md`가 색인. 요약:
|
||||||
- `.claude/base/` — 확정된 아키텍처/컨텍스트, plan/done 개념 없음. 먼저
|
- `.claude/base/` — 확정된 아키텍처/컨텍스트, plan/done 개념 없음. 먼저
|
||||||
`.claude/base/architecture.md`를 읽을 것.
|
`.claude/base/architecture.md`를 읽을 것.
|
||||||
|
- `.claude/reference/` — **[2026-08-07 신설]** base처럼 확정된 건 아니지만
|
||||||
|
base 문서가 근거로 인용하는 온디맨드 참고 자료(v1 내부 동작 스냅샷,
|
||||||
|
Fusion/Vide 비교 리서치) — 항상 읽을 필요는 없고 인용될 때만 열어볼 것.
|
||||||
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의.
|
- `.claude/research/` — 아직 착수 전, 사용자와 상의 필요한 설계 논의.
|
||||||
`tween-plan.md`/`existing-instance-bind-plan.md`/`debug-tooling-plan.md`/
|
`tween-plan.md`/`existing-instance-bind-plan.md`/`debug-tooling-plan.md`/
|
||||||
`ui-shorthand-plan.md`/`documentation-plan.md`/`documentation-content-map.md`/
|
`documentation-plan.md`/`documentation-content-map.md`/
|
||||||
`framework-comparison-findings.md` — 전부 후순위(급한 건 `tween-plan.md`
|
`framework-comparison-findings.md`/`additional-primitives-plan.md`(키 기반
|
||||||
|
동적 컬렉션 재조정만 남음) — 전부 후순위(급한 건 `tween-plan.md`
|
||||||
세부 옵션 정도). 최신 목록·우선순위는 `.claude/README.md`가 소스, 여기서
|
세부 옵션 정도). 최신 목록·우선순위는 `.claude/README.md`가 소스, 여기서
|
||||||
개수 반복 안 함(과거에 "두 개뿐"이라 적어놨다가 새 문서 추가될 때마다
|
개수 반복 안 함(과거에 "두 개뿐"이라 적어놨다가 새 문서 추가될 때마다
|
||||||
안 갱신되는 패턴이 반복돼서 아예 안 세기로 함).
|
안 갱신되는 패턴이 반복돼서 아예 안 세기로 함).
|
||||||
|
|
@ -676,3 +680,71 @@ M7 착수 시 `modifier-plan.md` 8번 참고하면 됨.
|
||||||
풀리는 문제 — `false`를 이벤트 disconnect 센티널로 쓴 선례처럼 `None`
|
풀리는 문제 — `false`를 이벤트 disconnect 센티널로 쓴 선례처럼 `None`
|
||||||
(가칭) 프리미티브를 도입하는 방향만 `base/modifier-plan.md` "2-1"절에
|
(가칭) 프리미티브를 도입하는 방향만 `base/modifier-plan.md` "2-1"절에
|
||||||
짧게 메모해두고 상세 설계는 다음 세션으로 미룸.
|
짧게 메모해두고 상세 설계는 다음 세션으로 미룸.
|
||||||
|
|
||||||
|
## 2026-08-07 네 번째 세션 — `.claude/` 코퍼스 전반 정리(폴더 재편, 승격, 기각 분리)
|
||||||
|
|
||||||
|
사용자가 코퍼스 전체를 훑고 "실제 코딩에 필요한가"를 기준으로 남길 것과
|
||||||
|
분리할 것을 판단해 달라고 요청 — 여러 문서에 쌓인 역전 이력/quad
|
||||||
|
자체와 무관한 배경자료/이미 기각된 후보가 뒤섞여 있어 컨텍스트 크기와
|
||||||
|
가독성 둘 다 해치고 있다는 문제의식. 아래 6가지를 처리, 전부 반영 완료:
|
||||||
|
|
||||||
|
1. **`reference/` 폴더 신설** — `quad-v1-architecture.md`,
|
||||||
|
`comparison-fusion-vide.md`를 `base/`에서 이동. 항상 읽어야 하는
|
||||||
|
결정사항(`base/`)과, 다른 문서가 근거로 인용할 때만 열어보면 되는
|
||||||
|
온디맨드 스냅샷/비교자료(`reference/`)를 분리 — 전자는 "결정 완료",
|
||||||
|
후자는 "결정이 아니라 결정의 근거"라는 차이. 전체 문서의 상호참조
|
||||||
|
경로도 전부 갱신함.
|
||||||
|
2. **`component-composition-plan.md`의 누적 역전 이력 트리밍** —
|
||||||
|
`StoreSource` 프록시 폐기 이력이 "원래 이랬다 → 이렇게 뒤집혔다"를
|
||||||
|
본문에서 장황하게 반복 서술하고 있었는데, 이미 `archive/
|
||||||
|
store-source-proxy-reversed.md`에 원문·이유·비교표가 전부 보존돼
|
||||||
|
있으므로 본문은 최종 확정만 남기고 포인터로 압축.
|
||||||
|
3. **`ui-shorthand-plan.md`를 `research/`→`base/`로 승격, 재작성** —
|
||||||
|
(a) 이미지 라운드 트릭 `RoundSize`는 완전히 드롭, 근거는
|
||||||
|
`archive/ui-shorthand-roundsize-dropped.md`로 분리(이 판단이 한 차례
|
||||||
|
"Corner/PaddingAll/Scale 전체가 불필요하다"로 잘못 일반화됐다가
|
||||||
|
정정된 이력도 같이 보존). (b) 이름을 v1 그대로(`Corner`/`PaddingAll`/
|
||||||
|
`Scale`)가 아니라 실제 Roblox Instance 이름과 맞춘 `UICorner`/
|
||||||
|
`UIPadding`/`UIScale`로 확정 — v1식 짧은 이름은 Modifier 체이닝
|
||||||
|
메소드와 겹쳐 "진짜 UICorner 숏핸드인지 그냥 비슷한 이름의 부가
|
||||||
|
Modifier인지" 구분이 안 된다는 사용자 지적 반영. (c) store-bind
|
||||||
|
가능성 명시 — v1에서도 가능했던 기능이고, Tween처럼 무거운 API
|
||||||
|
표면 없이 기존 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`엔 아직 실제로 열려있는
|
||||||
|
것(키 기반 동적 컬렉션 재조정) 하나만 남김.
|
||||||
|
5. **archive 제목 컨벤션을 둘로 분화** — 기존 `[역전됨]`(한 번 확정했다가
|
||||||
|
뒤집힌 것, `store-source-proxy-reversed.md`/`ref-phase-option-reversed.md`)과
|
||||||
|
새로 생긴 `[기각됨]`(확정한 적 없이 후보였다가 채택 안 된 것,
|
||||||
|
`batch-rejected.md`/`context-rejected.md`/`ui-shorthand-roundsize-dropped.md`)을
|
||||||
|
구분 — `README.md`의 `archive/` 폴더 기준 설명에 두 컨벤션 차이를
|
||||||
|
명시.
|
||||||
|
6. **`tween-plan.md` 보강** — `retract`가 Destroy 시엔 호출 안 된다는
|
||||||
|
사실을 상단 상태 요약에서도 짚도록 가시성 강화, `canExecute`(Destroy
|
||||||
|
시 처리)와 `retract`(값 교체 시 처리)가 서로 다른 문제를 다룬다는
|
||||||
|
점을 quadnomicon급 문서화 숙제로 메모(지금은 상세 설명 안 하고
|
||||||
|
메모만). 트윈 옵션 값 모양(raw `TweenInfo` vs 이름 붙은 편의
|
||||||
|
필드+기본값) 논의를 새로 열어둠 — Luau가 named call을 지원 안 해서
|
||||||
|
`TweenInfo.new(...)` 포지셔널 생성자가 읽기 어렵다는 문제의식,
|
||||||
|
소견은 편의 필드 쪽이지만 확정 아님, 나중 논의 대상으로만 남김.
|
||||||
|
|
||||||
|
**미해결로 남긴 것 — 임의로 결론내지 않음**: Effect가 `state:Effect()`
|
||||||
|
형태로 Observer를 확장하는 변형인지, 완전히 독립된 free function인지가
|
||||||
|
불명확함(사용자가 "확인 필요, 아니라면 논의해야 할 상태로 남겨두라"고
|
||||||
|
명시). 관련 하위 질문으로 `state:Observer(fn)`가 생성 시 `fn`을 즉시
|
||||||
|
1회 실행하는지도 문서 어디에도 명시돼 있지 않음이 이번에 드러남(Effect는
|
||||||
|
"즉시 1회 실행"이 스펙에 명시돼 있어 이 부분만 보면 둘이 겹쳐 보임).
|
||||||
|
`base/additional-primitives.md`의 "미해결" 절과 `.claude/question.md`
|
||||||
|
0번에 반영 — 구현 착수(M3~M4 전후) 전에 반드시 재확인할 것.
|
||||||
|
|
||||||
|
**다음 세션이 할 일**: 안 바뀜(위 2026-08-06 네 번째 세션 절 "다음 세션이
|
||||||
|
할 일" 참고, `ROADMAP.md` M0부터). 이번 세션은 순수 문서 정리라 설계
|
||||||
|
결정 자체는 늘지 않았음 — 단, M3 체크리스트에 `Blocker.luau` 항목이
|
||||||
|
하나 추가된 것과, 위 Effect/Observer 미해결 항목은 M3~M4 착수 전에
|
||||||
|
확인해야 함.
|
||||||
|
|
|
||||||
26
ROADMAP.md
26
ROADMAP.md
|
|
@ -52,12 +52,21 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
||||||
|
|
||||||
- [ ] `Dispatch/init.luau`(`process`/`retract` 엔진, `isHandlable` 우선순위 스캔)
|
- [ ] `Dispatch/init.luau`(`process`/`retract` 엔진, `isHandlable` 우선순위 스캔)
|
||||||
- [ ] `Handler.luau`(핸들러 계약 타입)
|
- [ ] `Handler.luau`(핸들러 계약 타입)
|
||||||
|
- [ ] `LifetimeHandle.luau`/`PerInstanceState.luau` **인터페이스만**(타입
|
||||||
|
계약, 실 구현 없음 — quad-roblox 실 구현은 M8) — 원래 M8에만
|
||||||
|
있었으나 M4(StoreBind의 `Connected` 확인)/M6(Slot의 `canExecute`)이
|
||||||
|
이미 이 인터페이스를 전제로 서술돼 있어 로드맵 순서가 역전돼
|
||||||
|
있었음(`pre-implementation-audit.md` 우선순위1-9, `question.md` 2번
|
||||||
|
— 2026-08-07 세 번째 세션에 반영)
|
||||||
- [ ] mock 대상 테스트
|
- [ ] mock 대상 테스트
|
||||||
|
|
||||||
## M3 — Store/State/Source
|
## M3 — Store/State/Source
|
||||||
|
|
||||||
- [ ] `Source.luau`/`State.luau`/`Store.luau`
|
- [ ] `Source.luau`/`State.luau`/`Store.luau`
|
||||||
- [ ] `store.key` dot-access 타입 추론 확인
|
- [ ] `store.key` dot-access 타입 추론 확인
|
||||||
|
- [ ] `Blocker.luau`(`base/additional-primitives.md` 참고 — 여러 Source를
|
||||||
|
한꺼번에 바꿔도 파생값 재계산/재대입이 한 번만 되게 하는 primitive,
|
||||||
|
State와 밀접히 연관돼 있어 같은 마일스톤에서 개발)
|
||||||
- [ ] mock 대상 테스트
|
- [ ] mock 대상 테스트
|
||||||
|
|
||||||
## M4 — 첫 end-to-end 반응형 업데이트
|
## M4 — 첫 end-to-end 반응형 업데이트
|
||||||
|
|
@ -87,12 +96,23 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
||||||
- [ ] `State<Modifier>` 조합 타입 차단 확인(`modifier-plan.md` 7번, UB 확정)
|
- [ ] `State<Modifier>` 조합 타입 차단 확인(`modifier-plan.md` 7번, UB 확정)
|
||||||
- [ ] `:Apply(factory)` 팩토리 함수 체이닝(`modifier-plan.md` 8번, 예약 키
|
- [ ] `:Apply(factory)` 팩토리 함수 체이닝(`modifier-plan.md` 8번, 예약 키
|
||||||
`Apply`가 제네릭 `__index` 필드 setter와 안 겹치는지 확인)
|
`Apply`가 제네릭 `__index` 필드 setter와 안 겹치는지 확인)
|
||||||
|
- [ ] 인라인 키로 modifier 필드를 명시적으로 지우는 문제 확인 — `None`
|
||||||
|
(가칭) 센티널 프리미티브 도입 여부(`modifier-plan.md` 2-1번, 아직
|
||||||
|
미정 — 착수 전 사용자 확인 필요, 확정 안 되면 이번 마일스톤은
|
||||||
|
스킵하고 다음으로 미뤄도 됨)
|
||||||
|
|
||||||
## M8 — Ref
|
## M8 — Ref
|
||||||
|
|
||||||
- [ ] `CreatedRef` 메커니즘(숫자 슬롯 참가자)
|
- [ ] `CreatedRef` 메커니즘(숫자 슬롯 참가자) + `PreRef`(children 배열
|
||||||
- [ ] `LifetimeHandle` 인터페이스 + quad-roblox 실제 구현(Instance 생존 확인)
|
전용, Modifier/Store 타입 차단, 위치 무관 호이스팅 pre-pass —
|
||||||
- [ ] `PerInstanceState` 인터페이스 + quad-roblox 실제 구현(weak-keyed table)
|
`base/bind-system-plan.md` "`phase` 옵션 폐기 → 위치로 표현,
|
||||||
|
`PreRef` 신설" 절)
|
||||||
|
- [ ] Ref 콜백/대기자 실행 루프(`type(v)=="thread"`면 resume+소진,
|
||||||
|
함수면 호출+유지 — 같은 배열 하나로 통합)
|
||||||
|
- [ ] `LifetimeHandle` quad-roblox 실제 구현(Instance 생존 확인, 인터페이스
|
||||||
|
자체는 M2로 이동됨)
|
||||||
|
- [ ] `PerInstanceState` quad-roblox 실제 구현(weak-keyed table, 인터페이스
|
||||||
|
자체는 M2로 이동됨)
|
||||||
|
|
||||||
## M9 — 컴포넌트 합성 레이어
|
## M9 — 컴포넌트 합성 레이어
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue