From 4f3badf414a21813c0ef66e4f6bb5692d98e8fc8 Mon Sep 17 00:00:00 2001 From: qwreey Date: Sat, 8 Aug 2026 11:38:29 +0900 Subject: [PATCH] =?UTF-8?q?docs(base):=20=EC=BD=94=ED=8D=BC=EC=8A=A4=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A0=95=ED=95=A9=EC=84=B1=20=EA=B0=90?= =?UTF-8?q?=EC=82=AC=20=EB=B0=98=EC=98=81,=20=EA=B8=B0=EA=B0=81=EB=90=9C?= =?UTF-8?q?=20=EB=8C=80=EC=95=88=20archive=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 병렬 서브에이전트 4개로 base/reference/research/archive 전체를 재감사해 발견한 14건 수정 — canExecute(inst,value) 시그니처 통일, Dispatch.process가 Dispatch 체인/retractUnder와 모순되던 서술 정정, "프로바이더"→Handler 잔재 정리, Overridden 오타, :Peek 반환 타입에 None 누락, Peek/isState 미정 표기 해소 누락, slot CRUD 의미론 갭 미표기, Relate 개명 반영 누락, pre-implementation-audit.md 자기모순(이미 해소된 1-2 재언급, 옛 UI 숏핸드 이름), documentation-content-map.md 신구 이름 자기모순 및 TagService/CollectionService 재발, README.md archive 색인 누락 행, agent-mistake.md 카테고리 태그 누락. modifier-plan.md 9-1번 절에 인라인으로 남아있던 기각된 Apply-mutable 대안 두 개의 전체 경위를 archive/modifier-apply-mutable-rejected.md로 이관하고 본문은 결론+포인터로 압축 — quadnomicon 개발로그 소재 확보, 컨텍스트 비대화 방지. Co-Authored-By: Claude Sonnet 5 --- .claude/README.md | 2 + .claude/archive/agent-mistake.md | 2 +- .../modifier-apply-mutable-rejected.md | 46 +++++++++++++++++++ .claude/base/architecture.md | 2 +- .claude/base/bind-system-plan.md | 14 ++++-- .claude/base/modifier-plan.md | 40 +++++----------- .claude/base/module-lifecycle-plan.md | 12 +++-- .claude/base/slot-plan.md | 7 +++ .claude/base/store-semantics.md | 3 +- .claude/base/tag-plan.md | 2 +- .claude/base/ui-shorthand-plan.md | 2 +- .claude/research/documentation-content-map.md | 6 +-- .claude/research/pre-implementation-audit.md | 15 +++--- .claude/research/tween-plan.md | 7 +-- 14 files changed, 104 insertions(+), 56 deletions(-) create mode 100644 .claude/archive/modifier-apply-mutable-rejected.md diff --git a/.claude/README.md b/.claude/README.md index ff5a135..e9fea7c 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -71,6 +71,8 @@ | `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/blocker-plan.md`)로 대체 | | `context-rejected.md` | **[기각됨, 2026-08-07 신설]** `Context`(트리 하위 암묵 전파) + 대안이던 레이어드 Store 둘 다 기각 — 명시적 타입 강제 Store 전달로 충분하다는 판단 | +| `modifier-apply-mutable-rejected.md` | **[기각됨, 2026-08-08 신설]** `Modifier.Apply`/setter를 mutable로 바꾸는 방안(및 "Apply 경계에서만 clone" 절충안) — 둘 다 형제 서브트리 오염 방지가 clone 비용 절감보다 우선이라 기각 | +| `tag-hash-key-model-reversed.md` | [역전됨] 구 `Tag` 모델(해시 파트 boolean 키, 태그 개수만큼 키 갱신) — 2026-08-08 세 번째 세션에서 array-part 값 객체(`Tag(...)`, `:Added`/`:Removed`/`:Contains`/`:Apply`/`Merged`) 모델로 완전히 대체됨 | | `agent-mistake.md` | **[에이전트 실수, 2026-08-07 신설]** 설계 반전이 아니라 에이전트가 문서 작성 중 개념을 혼동했다가 같은 세션 안에서 스스로 정정한 사례 모음(`canExecute`/`isHandlable` 혼동, `isSource` 불필요 오판) — CLAUDE.md 세션 로그의 중복 서술을 여기로 옮기고 포인터만 남김 | ## 참고 diff --git a/.claude/archive/agent-mistake.md b/.claude/archive/agent-mistake.md index 735299d..67c39ef 100644 --- a/.claude/archive/agent-mistake.md +++ b/.claude/archive/agent-mistake.md @@ -1,4 +1,4 @@ -# 에이전트 실수 기록 +# [에이전트 실수] 에이전트 실수 기록 CLAUDE.md 세션 로그 안에 흩어져 있던 "에이전트가 같은 세션 안에서 스스로 정정한 실수" 서술을 여기로 모음 — 최종 결론은 이미 각 `base/` 문서에 diff --git a/.claude/archive/modifier-apply-mutable-rejected.md b/.claude/archive/modifier-apply-mutable-rejected.md new file mode 100644 index 0000000..65a86b0 --- /dev/null +++ b/.claude/archive/modifier-apply-mutable-rejected.md @@ -0,0 +1,46 @@ +# [기각됨] Modifier `Apply`/setter를 mutable로 바꾸는 방안 (전체·절충안 둘 다) + +**상태**: 후보였다가 채택 안 됨(확정한 적 없이 검토 후 기각) — `base/ +modifier-plan.md` 9-1번 절에서 이 판단의 결론(판단 기준 자체는 "동질적/ +이질적"이 아니라 "계산 의존성 유무")만 남기고 아래 전체 경위는 이 문서로 +옮김. `batch-rejected.md`/`context-rejected.md`와 같은 카테고리 — +`quadnomicon` 소재 후보. + +## 배경 + +2026-08-07 다섯 번째 세션 후속. `Apply` 체이닝이 호출마다 clone을 만들기 +때문에, 항목 수천 개짜리 리스트 UI처럼 무거운 Modifier를 대량으로 +재생성하는 상황에서 이 clone 비용이 누적되는 게 아닌지 사용자가 우려 — +대안으로 (a) `Apply`/setter를 아예 mutable로 바꾸는 방안, (b) `Overridden`를 +"여러 값을 합칠 특수 상황"이 아니라 "성능 최적화 수단"으로 승격하는 방안을 +검토했음(이 문서는 (a)와 그 절충안만 다룸 — (b)는 기각되지 않고 "계산 +의존성 유무" 판단 기준으로 정리되어 `modifier-plan.md` 본문에 그대로 남음). + +## (a) `Apply`를 mutable로 바꾸는 방안 — 기각 + +3번 절에서 immutable+clone을 확정한 이유가 정확히 "같은 modifier +레퍼런스를 공유하는 형제 서브트리가 mutate로 오염되는 것"을 막기 +위해서였음 — 이건 특정 세션 판단이 아니라 2026-08-04부터 계속 지켜온 +하드 제약. `Apply`/setter가 mutable이면 여러 컴포넌트가 참조하는 공유 +테마 상수 하나에 어느 한쪽이 체이닝만 해도 다른 쪽까지 같이 바뀌는 +클래스의 버그가 그대로 돌아옴 — clone 비용 절감이 이 안전성보다 +우선순위가 높다고 볼 근거가 없어 기각. (단, `table.clone`은 Luau native +shallow-copy라 Modifier 필드 수(한 자리~여남은 개) 기준 개별 clone 비용 +자체는 이미 3번 절에서 무시 가능하다고 판단됨 — 이번에 새로 문제 삼는 건 +"한 번 비용의 크기"가 아니라 "체인 길이 × 인스턴스 수로 누적되는 clone +*횟수*"라는 별개 축.) + +## (a-1) 절충안 — "`Apply` 진입 시 한 번만 clone하고 그 안에서는 mutable로" — 검토했으나 기각 + +clone 횟수를 체인 길이만큼이 아니라 `Apply` 호출당 1번으로 줄이자는 +아이디어(`Apply` 경계에서만 복사, 내부 setter들은 그 복사본을 그대로 +mutate). + +**기각 이유**: 이렇게 해도 버그 클래스 자체가 안 없어짐 — `Apply`를 +거치지 않고 setter를 직접 호출하는 흔한 경로(`mod:FontSize(...)`처럼 +체이닝 자체가 아니라 단발 호출)는 여전히 mutable이라, 공유 레퍼런스에 +대고 단발 setter 하나만 불러도(예: 서브트리 어딘가에서 폰트 두께만 살짝 +바꾸는 경우) 그대로 오염됨 — "`Apply` 안에서는 안전, 밖에서는 안 안전"처럼 +**어디서 터지느냐만 달라질 뿐 문제 자체는 그대로 남는 비일관적인 +절충**이라 실익이 없음. 전부 clone하는 지금 방식이 버그 클래스를 균일하게 +없애는 유일한 방법 — 확정 유지. diff --git a/.claude/base/architecture.md b/.claude/base/architecture.md index 0cdca55..a157fe7 100644 --- a/.claude/base/architecture.md +++ b/.claude/base/architecture.md @@ -35,7 +35,7 @@ quad는 이제 "스크립트"가 아니라 **라이브러리**다. DOMless Roblo 5. **id 기반 전역 조회 폐지, Tag 시스템으로 대체.** v1의 `Store.GetObject(id)`/ `Frame "id" {}`류는 더 이상 없음 — "id 매핑이 비현실적"이라는 게 이유. 네임스페이싱 문제는 있지만 별도 네임스페이스 개념을 추가하면 라이브러리 - 복잡도가 너무 올라간다고 판단 — 당장은 TagService 그대로 사용. **대신 + 복잡도가 너무 올라간다고 판단 — 당장은 `CollectionService` 그대로 사용. **대신 Ref가 도입됨** — 단 Ref의 용도는 "id로 조회"가 아니라 "외부에서 이미 관리되고 있는 instance를 quad로 점진적으로 마이그레이션/래핑하기 위해 직접 참조를 얻는 것"(`base/bind-system-plan.md`의 Ref 절 참고) — 둘을 diff --git a/.claude/base/bind-system-plan.md b/.claude/base/bind-system-plan.md index 453ad95..46eaf18 100644 --- a/.claude/base/bind-system-plan.md +++ b/.claude/base/bind-system-plan.md @@ -220,10 +220,13 @@ NoneHandler.process(inst, k, v) = process(inst, k, nil) -- 재귀 재호출 - `Dispatch.getHandler(inst,k,v): Handler?` — 순수 스캔(`handler.isHandlable(inst,k,v)`+ `priority`), 부작용 없음. - `Dispatch.process(inst,k,v)` — 오케스트레이터: `getHandler` 호출 → - 이전에 이 키를 담당하던 핸들러와 다르면 이전 핸들러의 `retract` 호출 → - 새로 매치된 핸들러의 `.process` 호출. **재귀 재디스패치(Tween/일반 - store-bind/`NoneHandler`)는 전부 이 `Dispatch.process`를 다시 부르는 - 것** — 원래 있던 재귀 관례 그대로, 새로 바뀐 것 없음. + 매치된 핸들러를 `(inst,k)` 체인 꼬리에 push → 그 핸들러의 `.process` + 호출. **"이전 핸들러와 다르면 retract"라는 diff는 `Dispatch.process` + 자신의 일이 아님** — 재귀/래핑 핸들러(Tween/일반 store-bind/ + `NoneHandler`)가 재-dispatch 전에 스스로 `Dispatch.retractUnder(inst, + k, self, newV)`를 먼저 불러 자기 밑을 정리하는 책임을 짐(정확한 + 메커니즘·기각된 대안은 아래 "Dispatch 체인" 절 참고 — 전역 소유자 + 슬롯 하나로 diff하는 안은 래핑 핸들러에서 깨져서 기각됨). - `Dispatch.addHandler(handler: Handler)` — 핸들러를 우선순위 레지스트리에 등록. `Dispatch.process`/`getHandler`와 마찬가지로 base엔 인터페이스만 있고, quad-roblox의 concrete Handler들(PropertyHandler/EventHandler/ @@ -1448,7 +1451,8 @@ Modifier처럼 플래튼하지 않는가"는 설계 근거를 알고 싶은 사 - `base/store-semantics.md`에 있던 "`isInit=false`면 허용, `isInit=true`+ 생존확인 거짓이면 불허" 분기 초안은 폐기. state-invalidate 리스너 클로저도 `base/lifecycle-pattern.md`의 "생명 바인드 유틸"(canExecute - predicate)로 등록하면, 발화 시 `canExecute(handle)` 하나만 확인하고 거짓이면 + predicate)로 등록하면, 발화 시 `canExecute(inst, value)`(2026-08-08 세션 + 최종 시그니처) 하나만 확인하고 거짓이면 그냥 no-op — `isInit` 분기라는 별도 개념 자체가 불필요(사용자 확정: "canExecute 하나로 통일"). diff --git a/.claude/base/modifier-plan.md b/.claude/base/modifier-plan.md index 756fa30..7093350 100644 --- a/.claude/base/modifier-plan.md +++ b/.claude/base/modifier-plan.md @@ -365,33 +365,14 @@ Modifier가 들고 있는 모든 필드는 항상 "그 시점에 이미 완전 **동기**: `Apply` 체이닝이 호출마다 clone을 만들기 때문에, 항목 수천 개짜리 리스트 UI처럼 무거운 Modifier를 대량으로 재생성하는 상황에서 이 clone 비용이 누적되는 게 아닌지 사용자가 우려 — 대안으로 (a) `Apply`/setter를 -아예 mutable로 바꾸는 방안, (b) `Overridden`를 "여러 값을 합칠 특수 상황"이 -아니라 "성능 최적화 수단"으로 승격하는 방안을 검토. +아예 mutable로 바꾸는 방안(과 그 절충안), (b) `Overridden`를 "여러 값을 +합칠 특수 상황"이 아니라 "성능 최적화 수단"으로 승격하는 방안을 검토. -**(a) `Apply`를 mutable로 바꾸는 방안 — 기각.** 3번 절에서 immutable+clone을 -확정한 이유가 정확히 "같은 modifier 레퍼런스를 공유하는 형제 서브트리가 -mutate로 오염되는 것"을 막기 위해서였음 — 이건 특정 세션 판단이 아니라 -2026-08-04부터 계속 지켜온 하드 제약. `Apply`/setter가 mutable이면 여러 -컴포넌트가 참조하는 공유 테마 상수 하나에 어느 한쪽이 체이닝만 해도 다른 -쪽까지 같이 바뀌는 클래스의 버그가 그대로 돌아옴 — clone 비용 절감이 -이 안전성보다 우선순위가 높다고 볼 근거가 없어 기각. (단, `table.clone`은 -Luau native shallow-copy라 Modifier 필드 수(한 자리~여남은 개) 기준 -개별 clone 비용 자체는 이미 3번 절에서 무시 가능하다고 판단됨 — 이번에 -새로 문제 삼는 건 "번 비용의 크기"가 아니라 "체인 길이 × 인스턴스 수로 -누적되는 clone *횟수*"라는 별개 축.) - -**(a-1) 절충안 — "`Apply` 진입 시 한 번만 clone하고 그 안에서는 -mutable로" — 검토했으나 기각.** clone 횟수를 체인 길이만큼이 아니라 -`Apply` 호출당 1번으로 줄이자는 아이디어(`Apply` 경계에서만 복사, 내부 -setter들은 그 복사본을 그대로 mutate). **기각 이유**: 이렇게 해도 버그 -클래스 자체가 안 없어짐 — `Apply`를 거치지 않고 setter를 직접 호출하는 -흔한 경로(`mod:FontSize(...)`처럼 체이닝 자체가 아니라 단발 호출)는 -여전히 mutable이라, 공유 레퍼런스에 대고 단발 setter 하나만 불러도(예: -서브트리 어딘가에서 폰트 두께만 살짝 바꾸는 경우) 그대로 오염됨 — -"`Apply` 안에서는 안전, 밖에서는 안 안전"처럼 **어디서 터지느냐만 -달라질 뿐 문제 자체는 그대로 남는 비일관적인 절충**이라 실익이 없음. -전부 clone하는 지금 방식이 버그 클래스를 균일하게 없애는 유일한 방법 — -확정. +**(a) `Apply`/setter를 mutable로 바꾸는 방안(및 "`Apply` 경계에서만 clone" +절충안) — 둘 다 검토 후 기각.** 3번 절의 immutable+clone 하드 제약(형제 +서브트리 오염 방지)이 clone 비용 절감보다 우선순위가 높다는 결론, 절충안도 +"어디서 터지느냐만 달라질 뿐 문제 자체는 남는" 비일관적 타협이라 기각 — +전체 경위·반박 논리는 `archive/modifier-apply-mutable-rejected.md` 참고. **(b) 판단 기준 — "동질적 vs 이질적 프로퍼티"가 아니라 "필드 간 계산 의존성 유무"로 명시.** 사용자가 처음엔 "동질적(폰트 굵기 보정처럼 연관된 @@ -461,7 +442,7 @@ Modifier 값을 변수/모듈 상수로 만들어 재사용하는, 기존에도 시점에 실 테스트 결과에 따라 다시 좁히는 걸 목표로 로드맵에 남김 (`ROADMAP.md` M7). -**`:Peek<>(key): T | State | nil`** — Modifier 필드를 확정하지 +**`:Peek<>(key): T | State | None | nil`** — Modifier 필드를 확정하지 않고 그대로 읽는 접근자. 이름을 `Get`이 아니라 `Peek`로 정한 이유: 이 프로젝트 전역에서 `State:Get()`은 "확정한다"(pull + recompute + 최종값 반환)는 의미로 이미 자리잡았는데, Modifier의 읽기는 정반대(들고 있는 @@ -508,5 +489,6 @@ Source도 같이 잡아줌 — **[2026-08-07 여덟 번째 세션 정정] `isSou - **[해소됨]** `Overridden` 이름 — 2026-08-08 세션에서 확정(`Add`/`Remove` →`Added`/`Removed`, `Merge`→`Merged`와 같은 분사형 네이밍 컨벤션에 맞춰 불규칙동사 `override`의 정확한 과거분사를 씀, `Overrided`는 오기). - `Peek`/`isState` — 동작은 위 9번 절에서 확정, 정확한 이름은 다른 - 가칭들과 마찬가지로 `.claude/question.md`의 용어 정리 라운드까지 잠정. + `Peek`/`isState` — 동작은 위 9번 절에서 확정, 이름도 2026-08-08 다섯 + 번째 세션(`.claude/question.md` 용어 정리 라운드)에서 더 나은 대안 없어 + 현재 이름 그대로 최종 확정됨. diff --git a/.claude/base/module-lifecycle-plan.md b/.claude/base/module-lifecycle-plan.md index edcb1eb..ce940ae 100644 --- a/.claude/base/module-lifecycle-plan.md +++ b/.claude/base/module-lifecycle-plan.md @@ -1,4 +1,4 @@ -# 모듈 라이프사이클 — 프로바이더 패턴, bind/store는 누가 구현하는가 (base로 승격됨) +# 모듈 라이프사이클 — Handler 패턴, bind/store는 누가 구현하는가 (base로 승격됨) **상태**: base — "누가 store를 구현하는가"까지 포함해 전부 확정되어 `research/`에서 승격됨(`base/architecture.md`의 "구현 착수: 소스 트리 구조 @@ -14,10 +14,12 @@ bind는 누가 어떻게 구현" / "스토어는 누가 구현해…" 절. 확 Slot과 맞물려서 잘 생각해서 구현해야 하는 부분. **기울어진 방향**: mount가 처리하는 게 맞아 보이지만, 그러면 확장성이 있을지가 문제. 결론: **표준 구현체는 인터페이스만 두고, 실제 구현은 `quad-roblox` 같은 백엔드 서브패키지가 해당 -인터페이스를 구현**. 런타임에 프로바이더로 Roblox를 주입받는 방향(반대로 -"프로바이더로 base를 받는" 게 아니라) — 이유: 여긴 가상돔이 없어서, base -쪽이 "누가 실제로 그려주는지" 모르는 채로 있다가 Roblox 프로바이더를 주입받는 -모양이 더 자연스러워 보임. +인터페이스를 구현**. 런타임에 Handler로 Roblox를 주입받는 방향(반대로 +"Handler로 base를 받는" 게 아니라) — 이유: 여긴 가상돔이 없어서, base +쪽이 "누가 실제로 그려주는지" 모르는 채로 있다가 Roblox Handler를 주입받는 +모양이 더 자연스러워 보임. (이름 자체는 이후 "Handler"로 확정 — +`base/bind-system-plan.md`의 핸들러 계약 절 참고, 이 문서는 여전히 +초안 당시 표현인 "프로바이더"로 쓰여 있던 걸 정정.) ## pluggable 플러그 초기화는 누구 몫인가 diff --git a/.claude/base/slot-plan.md b/.claude/base/slot-plan.md index d3092fc..f0a969a 100644 --- a/.claude/base/slot-plan.md +++ b/.claude/base/slot-plan.md @@ -134,3 +134,10 @@ Slot은 바인딩되는 순간 그 안의 요소를 전부 own해버리는 데 - **여러 Slot이 형제로 섞일 때 순서 보장**은 아직 열려있음(위 "여러 Slot이 섞일 때 순서 보장" 절 참고) — Roblox 단일 백엔드로는 급하지 않음, Slot 코어 로직 구현 시점에 재검토. +- **`add`/`remove`/`clear` CRUD 의미론 자체가 아직 정의 안 됨** — 위 + "개념" 절이 이 세 연산을 뮤터블 메타 배열에 지원되는 것처럼 나열만 + 하고 정확한 동작(예: `add`가 위치를 지정하는지, `remove`가 값 동등성 + 기준인지 참조 기준인지, `clear`가 재마운트 가능한 자리를 남기는지)은 + 정의돼 있지 않음. `research/pre-implementation-audit.md`가 이미 지적한 + 갭이고, 2026-08-07 아홉 번째 세션에서 사용자가 직접 다루기로 보류함 — + Slot 코어 로직 구현 착수 전 반드시 확정 필요. diff --git a/.claude/base/store-semantics.md b/.claude/base/store-semantics.md index c493e3c..15d4cb1 100644 --- a/.claude/base/store-semantics.md +++ b/.claude/base/store-semantics.md @@ -31,7 +31,8 @@ purity-and-effects-plan.md`와 연결됨). 어떻게 되는가 — 별도 메커니즘을 새로 만들 필요 없이, `base/ lifecycle-pattern.md`의 "생명 바인드 유틸"(canExecute predicate)을 state- invalidate 리스너 클로저 등록에도 그대로 재사용하면 됨: 발화 시 -`canExecute(handle)` 하나만 확인, 거짓이면 no-op. 한때 검토했던 `isInit=false`면 +`canExecute(inst, value)`(2026-08-08 세션 최종 시그니처 — `base/ +lifecycle-pattern.md` 참고) 하나만 확인, 거짓이면 no-op. 한때 검토했던 `isInit=false`면 허용/`isInit=true`+생존확인 거짓이면 불허 분기 초안은 폐기 — `canExecute` 하나로 통일(사용자 확정). 상세는 `base/bind-system-plan.md`의 "Store/State/Source 온톨로지" 절 참고. diff --git a/.claude/base/tag-plan.md b/.claude/base/tag-plan.md index 02478ea..eb01544 100644 --- a/.claude/base/tag-plan.md +++ b/.claude/base/tag-plan.md @@ -22,7 +22,7 @@ tag:Contains(name): boolean -- 멤버십 확인 tag:Apply(factory): U -- factory(self) 체이닝 설탕(Modifier와 동일 패턴) Tag.Merged(tag1, tag2, ...): Tag -- 여러 Tag의 합집합(무손실). Modifier의 Overridden(필드 단위 덮어쓰기, 손실 있음)와 - 다른 연산이라 이름도 다름 — Override는 + 다른 연산이라 이름도 다름 — Overridden은 "이미 계산된 걸 합침", Merged는 "집합을 합침" ``` diff --git a/.claude/base/ui-shorthand-plan.md b/.claude/base/ui-shorthand-plan.md index 67fd295..cee3d1c 100644 --- a/.claude/base/ui-shorthand-plan.md +++ b/.claude/base/ui-shorthand-plan.md @@ -121,7 +121,7 @@ base가 범용 유틸로 제공하기로 확정한 per-instance weak-keyed 저 ## 남은 열린 질문 (단순화 후보, 사소함) -- Corner/PaddingAll/Scale 3개 거의 동일한 형태의 Handler를 각각 만들지, +- UICorner/UIPadding/UIScale 3개 거의 동일한 형태의 Handler를 각각 만들지, `{key -> {ChildClassName, ChildDefaultName, Property, wrap=fn}}` 룩업 테이블로 구동되는 단일 `Handlers/InstanceShorthand.luau`로 통합할지 — `research/pre-implementation-audit.md` 3-2번 참고, 강제 사항 아님, diff --git a/.claude/research/documentation-content-map.md b/.claude/research/documentation-content-map.md index 7379632..af1986c 100644 --- a/.claude/research/documentation-content-map.md +++ b/.claude/research/documentation-content-map.md @@ -45,7 +45,7 @@ ### architecture.md - 초심자: DOMless 즉시 Instance 생성 모델 / 특수 바인드 키 / Ref 기본 개념 / modifier 기본 사용법(스타일링) / Store·State·Source 온톨로지 핵심 동작 / quad-base·quad-roblox 패키지 구조 존재 사실 -- api: Class 함수형+`:` 체이닝 예외 규칙 / store 바인드=전체 교체 의미론 / Tag/retract(TagService 기반) / modifier 병합 우선순위 규칙(→심화: CSS cascade 회피 근거) / PropertyChangedSignal이 pluggable 핸들러로 구현 / Source·State·Store 타입 정의 +- api: Class 함수형+`:` 체이닝 예외 규칙 / store 바인드=전체 교체 의미론 / Tag/retract(CollectionService 기반) / modifier 병합 우선순위 규칙(→심화: CSS cascade 회피 근거) / PropertyChangedSignal이 pluggable 핸들러로 구현 / Source·State·Store 타입 정의 - 심화: Class가 OOP 아닌 함수형인 이유 / metatable 체이닝 폐기 이유(v1 clone 문제) / id 기반 전역 조회 폐지 이유 / Style(Default) 시스템 폐기→modifier 대체 근거 / 멀티 백엔드(GTK 등) 지향 이유 / push-invalidate·pull-recompute 전파 모델 상세, 다이아몬드 의존성 해결 근거 - skip: Tracker 미구현, lang 모듈 분리, Signal 클래스 미구현 판단 과정, 소스 트리·모노레포 구조, 테스트 전략(mock 설계) @@ -92,9 +92,9 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부 - skip: 세션 날짜/확정 이력, 문서 승격/정정 안내 ### store-semantics.md / tween-plan.md / ui-shorthand-plan.md -- 초심자: Store 생성+`myStore.key = value` 문법 / `store.key`로 State 얻기 개념 / Tween 기본 바인드 키+취소 기본 동작 / UI 숏핸드 인라인 키 기본 예시(`Frame { PaddingAllOffset = 50 }`) +- 초심자: Store 생성+`myStore.key = value` 문법 / `store.key`로 State 얻기 개념 / Tween 기본 바인드 키+취소 기본 동작 / UI 숏핸드 인라인 키 기본 예시(`Frame { UIPaddingOffset = 50 }`) - api: `:With`+`:Compute` 시그니처(→심화) / `source:Emit()` 존재+"Get() 결과 캐시 금지" 캐비엇(버그 유발 포인트라 api에도 명시 가치 있음, →심화; 2026-08-06 후속 세션에서 `Store:Emit(key)`→`source:Emit()`로 호출부 변경, `store-semantics.md` 참고) / Tween 핸들러가 Instance 직접 받음(Ref 불필요) / retract는 Destroy 시 호출 안 됨(→심화) / UI 숏핸드 키 목록 레퍼런스 표 / Modifier와 순수 인라인 키 동등성 -- 심화: Source·Store·State·Observer 온톨로지(독립 프리미티브 vs 파생 데이터 원칙, 생성자 모양 근거) / `Emit`이 Source 전용인 이유(디버깅 그래프 무결성) / `Store`의 T가 Modifier 불가인 이유 / Tween을 반응 그래프 밖 특수 bind key로 둔 이유(Fusion 반면교사) / RoundSize 포팅 불필요 vs Corner/PaddingAll/Scale 필요 이유 / "작고 opt-in 아닌 편의 기능은 코어 포함" 원칙 +- 심화: Source·Store·State·Observer 온톨로지(독립 프리미티브 vs 파생 데이터 원칙, 생성자 모양 근거) / `Emit`이 Source 전용인 이유(디버깅 그래프 무결성) / `Store`의 T가 Modifier 불가인 이유 / Tween을 반응 그래프 밖 특수 bind key로 둔 이유(Fusion 반면교사) / RoundSize 포팅 불필요 vs UICorner/UIPadding/UIScale 필요 이유 / "작고 opt-in 아닌 편의 기능은 코어 포함" 원칙 - 열린 질문(문서화 보류): tween-plan.md의 오버라이드/삭제후재시작/끝점이동 옵션 키 이름 미정 / ui-shorthand의 RoundSize 완전 드롭 여부 - skip: 세션 정정 이력, v1 소스 조사 경위 diff --git a/.claude/research/pre-implementation-audit.md b/.claude/research/pre-implementation-audit.md index b1e7633..c2558af 100644 --- a/.claude/research/pre-implementation-audit.md +++ b/.claude/research/pre-implementation-audit.md @@ -518,12 +518,12 @@ M11 착수 시. 쉬워진다든가)가 있다면 한 줄 추가하고, 없다면 "그냥 클로저 업밸류를 쓰라"는 문서화 패턴으로 대체해 API 표면 자체를 줄이는 걸 검토. -### 3-2. Corner/PaddingAll/Scale 개별 Handler 대신 데이터 테이블 구동 단일 제네릭 Handler +### 3-2. UICorner/UIPadding/UIScale 개별 Handler 대신 데이터 테이블 구동 단일 제네릭 Handler **위치**: `base/ui-shorthand-plan.md` "메커니즘 — 새 아키텍처 개념 불필요" 절. -**문제**: 문서는 "Corner/PaddingAll/Scale 같은 특수 키를 인식하는 +**문제**: 문서는 "UICorner/UIPadding/UIScale 같은 특수 키를 인식하는 Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴 값 하나 → 고정 이름 자식 찾기/생성 → 프로퍼티 세팅)의 Handler를 각각 만드는 그림이다. 문서 자체가 "앞으로 비슷한 제안이 오면 이 선례를 따르라"고 @@ -531,8 +531,8 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴 선형으로 늘어나는 구조. **제안**: `{key -> {ChildClassName, ChildDefaultName, Property, wrap=fn}}` -형태의 룩업 테이블 하나로 구동되는 단일 `Handlers/InstanceShorthand.luau` -로 통합하는 안을 검토. 새 shorthand 키 추가가 "테이블에 항목 하나 추가"로 +형태의 룩업 테이블 하나로 구동되는 단일 `Handlers/InstanceShorthand.luau`로 +통합하는 안을 검토. 새 shorthand 키 추가가 "테이블에 항목 하나 추가"로 끝나 M10 이후 유지보수 비용이 줄어듦. 강제 사항 아님, 구현 시점에 결정할 정도의 사소한 개선 후보. @@ -595,8 +595,11 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴 반영 완료. 1-10(`store.key` 레코드 필드 타이핑)을 M0로 앞당기는 것 검토, 1-11(Modifier `__index` 트릭)도 비용이 낮으니 M0 후보로 포함 검토는 계속 열려있음. -- **M2(Dispatch) 착수 전**: 1-2, 1-3, 1-4를 한 번에 확정(전부 base - dispatch 엔진의 에러/상태관리 규칙이라 같이 결정하는 게 효율적). +- **M2(Dispatch) 착수 전**: 1-3, 1-4를 한 번에 확정(전부 base dispatch + 엔진의 에러/상태관리 규칙이라 같이 결정하는 게 효율적). 1-2는 2026-08-08 + 세 번째 세션에 Dispatch 체인+`retractUnder`로 이미 해소됨(위 1-2번 항목 + 참고) — 남은 건 M2 스파이크에서 다단 체인 케이스가 실제로 맞게 + 동작하는지 실측하는 것뿐. - **M2/M3 착수 전**: 1-6(canExecute 실제 구현) 실측(1-9는 반영 완료, 위 항목 참고). - **나머지**: 해당 마일스톤 착수 시점에 이 문서를 다시 열어 관련 항목만 diff --git a/.claude/research/tween-plan.md b/.claude/research/tween-plan.md index c1fb808..9db5085 100644 --- a/.claude/research/tween-plan.md +++ b/.claude/research/tween-plan.md @@ -69,9 +69,10 @@ Ref나 네임스페이스드 조회가 필요하지 않음. Tween의 store-bind 라이브러리가 강제하지 않고, `[Tween(key, tweenData, {onOverride=...})]`처럼 키 설정으로 사용자가 고를 수 있게 열어둠 — `retract(inst, k, v)`가 이전 값(v)을 받으므로 여기서 선택된 동작을 구현. `retract`가 접근해야 할 "이전에 -생성한 실제 Tween 객체"는 `base/bind-system-plan.md`가 말하는 base 제공 -범용 유틸(`inst`를 키로 하는 weak-keyed per-instance 상태 저장소)에 담아두면 -됨. **GC 확인(2026-08-07 여섯 번째 세션, 사용자 제안 검증)**: 이 저장소는 +생성한 실제 Tween 객체"는 `base/relate-plan.md`가 정의하는 `Relate`(`inst`를 +weak 키로 하는 범용 릴레이션 프리미티브, 옛 가칭 `base.perInstanceState`를 +대체)에 담아두면 됨. **GC 확인(2026-08-07 여섯 번째 세션, 사용자 제안 +검증)**: 이 저장소는 `inst`로 weak-keyed된 바깥 릴레이션 안에 `k`별 안쪽 릴레이션이 중첩된 구조라, `inst`가 죽으면 그 안에 담긴 Tween 인스턴스 릴레이션도 별도 정리 로직 없이 같이 GC됨 — `base/bind-system-plan.md`의 "핸들러 내부