diff --git a/.claude/base/lifecycle-hooks-plan.md b/.claude/base/lifecycle-hooks-plan.md index 54a9b3e..7c6be46 100644 --- a/.claude/base/lifecycle-hooks-plan.md +++ b/.claude/base/lifecycle-hooks-plan.md @@ -64,6 +64,11 @@ local function OnDestroyed(fn: () -> ()): EffectHandle end ``` +*(위 시그니처의 `Instance`는 읽기 편하라고 quad-roblox 기준으로 적은 +것 — 이 셋은 quad-base 소속이므로 실제 선언은 `Ref`가 그렇듯 백엔드 +Instance 타입을 모르는 제네릭/불투명 타입 자리로 남음. `bindLifetime(inst, +value)`류 base 유틸을 문서가 `inst`라고만 부르는 것과 같은 관례.)* + 호출 즉시 `PreRef():Callback(fn)`/`PostRef():Callback(fn)`/`Effect(...)`가 실행되고, children 배열에 실제로 놓이는 건 **그 결과인 `PreRef`/`PostRef`/ `EffectHandle` 인스턴스 자체**임 — `OnCreated`라는 이름이나 개념은 이 diff --git a/.claude/luau-test/README.md b/.claude/luau-test/README.md index bc93afd..f2769da 100644 --- a/.claude/luau-test/README.md +++ b/.claude/luau-test/README.md @@ -79,7 +79,7 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해 | `10-roblox-studio-checks.server.luau` (Studio 전용) | **[⚠️ 2026-08-14 다섯 번째 세션: A 섹션이 폐기된 모델을 검증 중 → `rewrite-required/`]** (A) `bindLifetime`/`unbindLifetime`/`canExecute`의 gcconn 트릭 + 이중 바인딩 게이트(Destroy 시 Connected 전환 포함), (B) Attribute의 Instance 참조 타입 지원, (C) CollectionService 태그/GetTagged 왕복. **A는 재작성 대상** — 파일 속 `canBound`, `bindLifetime`의 `value.Subscribed = true` 세팅, 2-인자 `canExecute(inst, value)`는 전부 폐기됨(게이트는 `if canExecute(v) then error(...) end` 하나, `canExecute`는 `value` 단독 1-인자, gcconn/gchold는 **Instance 생성 시점**에 생성). **[2026-08-13]** A 섹션 앞부분(ClassName 신호 미발화, Destroy 시 Connected 즉시 전환)은 사용자 자작 스크립트로 부분 확인됐고 **새 모델에서도 그대로 유효**(오히려 더 중요 — `canExecute`가 `.Connected`를 직접 읽는 게 leaf 경로 판정의 전부), `audit/gcconn-trick-verification.md` 참고. 이중 바인딩 게이트/재바인딩 허용/B/C는 이 공식 파일로 아직 확인 안 됨 | `lifecycle-pattern.md` "`bindLifetime`/`canExecute`/`unbindLifetime` — 확정", `archive/canexecute-inst-arg-reversed.md`, `source-state-plan.md` "이중 바인딩 금지", CLAUDE.md 2026-08-06 세션, `debug-tooling-plan.md` | | `11-modifier-illegal-value-error.luau` | Modifier 필드에 Ref/PreRef/Observer/Effect/Slot/Modifier가 들어오면 즉시 error, State/Source가 확정하는 값이 Modifier면 즉시 error(2026-08-09 세션에 "UB"에서 전환된 규칙) | `modifier-plan.md` "핸들러 계층 값 즉시 error" 절 + 7번 절 | | `12-type-attribute-generic-key-narrowing.luau` (타입체크 전용) | `[AttributeKey<> "name"] = value`(구 `Attribute<>`)처럼 제네릭 DI 키를 쓸 때 `value`의 타입이 실제로 `T`로 좁혀지는지 — base 문서 자신이 "미검증"이라 명시한 항목 | `attribute-plan.md` "[실측 필요, M0/M10]" (2026-08-09 열한 번째 세션 신설) | -| `13-type-ref-preref-subtype.luau` | (A, 타입) `PreRef`가 `Ref`를 구조적으로 만족하는지, (B, 런타임) `isRef`/`isPreRef` 합성이 재정정대로 동작하는지(`isRef(preRefInstance)`가 이제 `true`) + Leaf 핸들러가 `isRef(v) and not isPreRef(v)`로 명시적으로 좁혀야 하는 이유 | `brand-plan.md`의 `Brand` 절(2026-08-09 열한 번째 세션 재정정) | +| `13-type-ref-preref-subtype.luau` | (A, 타입) `PreRef`가 `Ref`를 구조적으로 만족하는지, (B, 런타임) `isRef`/`isPreRef` 합성이 재정정대로 동작하는지(`isRef(preRefInstance)`가 이제 `true`) + Leaf 핸들러가 `isRef(v) and not isPreRef(v)`로 명시적으로 좁혀야 하는 이유. **[2026-08-14 아홉 번째 세션] 재작성 시 `PostRef`도 같이 커버할 것** — 같은 `Ref` 런타임 재사용 + 브랜드 태그만 다른 형제라 A/B 둘 다 그대로 확장되고, Leaf predicate도 `isRef(v) and not isPreRef(v) and not isPostRef(v)`로 늘어남 | `brand-plan.md`의 `Brand` 절(2026-08-09 열한 번째 세션 재정정) | | `14-type-nilable-default-overload.luau` (타입체크 전용) | `Source(default)`/`Ref(default)`의 `default` 생략이 `T`가 nilable일 때만 안전하다는 캐비엇을, 함수 오버로드(교차 타입)로 실제로 타입 레벨에서 막을 수 있는지 | `source-state-plan.md` "State는 쓰기 대상이 아님" 절의 `default` 생략 캐비엇 | | `15-type-compute-trailing-deps-typepack.luau` (타입체크 전용) | `:Compute(fn, ...)`의 trailing deps를 `fn`에 위치 인자(lazy State 핸들)로도 노출하는 확장, 최종 시그니처 `fn(self, previous?, ...deps)` — 이형(heterogeneous) 다중 deps를 제네릭 타입 팩(`U...`)으로 표현 가능한지, `previous?`가 팩 앞(정정된 순서)에서만 통과하고 팩 뒤(옛 순서)에서는 막히는지 | `source-state-plan.md` "trailing deps를 fn에 lazy positional 인자로도 노출" 절(2026-08-11 후속 세션, 순서는 같은 날 세 번째 세션에 정정) | | `16-type-store-key-typefunction.luau` (타입체크 전용) | `Store`가 `T`의 각 필드를 `Source`로 감싼 타입을 Luau `type function`(`types.newtable`/`:setproperty`/`ty:properties()`)으로 실제 합성 가능한지, 결과가 구조적으로 `Source` 필드를 만족하는지 | `typing-limits.md` "`store.key` 레코드 필드 타이핑" 절(2026-08-12 열일곱 번째 세션), `pre-implementation-audit.md` 1-10 | diff --git a/.claude/research/documentation-content-map.md b/.claude/research/documentation-content-map.md index a146e29..2f2854f 100644 --- a/.claude/research/documentation-content-map.md +++ b/.claude/research/documentation-content-map.md @@ -7,6 +7,17 @@ 시점은 여전히 구현 우선(`CLAUDE.md` "지금 할 일" 1번). 나중에 실제 문서화를 시작할 때 이 맵을 목차/우선순위표로 쓰면 됨. +> **⚠️ [2026-08-14 아홉 번째 세션 감사] 이 서베이는 2026-08-06 시점의 +> `base/` 파일 구성을 기준으로 작성됐음.** 그 뒤 `bind-system-plan.md`가 +> 세 번(2026-08-13 아홉 번째 / 2026-08-13 열네 번째 / 2026-08-14 일곱 번째 +> 세션) 쪼개져 지금은 203줄짜리 분할 색인만 남았으므로, **아래에서 +> `bind-system-plan.md`를 가리키는 포인터는 대부분 실제로는 +> `source-state-plan.md`/`store-plan.md`/`ref-plan.md`/`event-plan.md`/ +> `brand-plan.md`/`dispatch-core-plan.md` 중 하나를 뜻함**(그 문서 맨 위의 +> 분할 표에서 어디로 갔는지 바로 찾을 수 있음). 콘텐츠 분류 자체는 여전히 +> 유효해서 경로만 이 규칙으로 읽으면 되고, 실제 문서화 착수 시점에 한 +> 번에 정규화할 것. + **api↔심화 연결 원칙(사용자 확정)**: api 문서는 항목마다 설명을 간략하게 유지하고, 근거·내부 동작까지 파고드는 내용은 심화 섹션으로 링크("더 알아보기 → 심화")하는 방식으로 연결. 아래 [api] 항목 중 "→심화"가 붙은 것들이 이 @@ -205,17 +216,28 @@ additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**: `(v=Ref)` 매치 핸들러로 처리돼 다른 핸들러들과 같은 우선순위 스캔에 참여한다는 의미에서 "hook"(순서 등록 가능, 다른 값으로 교체되면 `retract`로 취소됨)에 가깝고, `PreRef`는 그 스캔 밖의 - 고정 pre-pass라는 의미에서 "pre-hook"(항상 최우선 고정, 순서/취소 - 개념 자체가 다름)에 가깝다는 구분 — quadnomicon 에세이로 쓸 때 이 + 고정 pre-pass에서 fire된다는 의미에서 "pre-hook"(항상 최우선 고정, + 취소 개념 자체가 다름)에 가깝다는 구분 — quadnomicon 에세이로 쓸 때 이 "hook"/"pre-hook" 용어 자체를 채택할지만 아직 열려있음(복수 `PreRef` 간 순서는 2026-08-07 아홉 번째 세션에서 해소됨 — 배열 index 순서 - 그대로, 별도 규칙 없음, `ref-plan.md` "PreRef" 절 참고). + 그대로 **보장**, 별도 규칙 없음, `ref-plan.md` "PreRef" 절 참고. + 2026-08-14 아홉 번째 세션에 이 보장을 잠깐 미보장으로 뒤집었다가 + 철회한 왕복이 있음 — `archive/preref-order-unguaranteed-withdrawn.md`, + 이것도 에세이 소재). + **[2026-08-14 아홉 번째 세션] `PostRef`가 생겨 이 프레이밍은 3항이 됨** + — "pre-hook / hook / post-hook"으로 자연히 확장되고, 특히 + `PostRef`("두 패스가 전부 끝난 뒤")가 **자기 서브트리 완성은 보장하되 + 자기가 부모에 붙기 전**이라는 점은 React `componentDidMount`와의 대조 + 소재로 좋음(`ref-plan.md`의 "`PostRef`" 절, `base/lifecycle-hooks-plan.md`). **[해소됨, 2026-08-12 여섯 번째 세션]** 취소 가능 여부 — PreRef는 - 구조적으로 `retract` 체인에 아예 안 올라가므로 취소 개념 자체가 없고, - 대신 이미 fire된 PreRef를 재사용하면(두 번째 construction에 다시 - 놓으면) 즉시 `error` — "1회용, 재할당 불가"로 확정. `ref-plan.md` - "동적 경로로 도착한 PreRef는 런타임에도 명시적으로 에러" 절 바로 아래 - "PreRef는 '취소'라는 개념이 없다" 항목 참고. + 취소 개념 자체가 없고(**[정정, 2026-08-14 두 번째 세션]** 근거가 + "`retract` 체인에 아예 안 올라가서"에서 **"그 자리의 retract가 하드코딩된 + no-op이라 되돌릴 상태 자체가 없어서"**로 바뀜 — `ProcessedPreRef` 신설로 + 소진된 슬롯도 이제 정상 `Dispatch.process` 경로를 타 체인에 올라감, + `PostRef`도 동일), 대신 이미 fire된 PreRef를 재사용하면(두 번째 + construction에 다시 놓으면) 즉시 `error` — "1회용, 재할당 불가"로 확정. + `ref-plan.md` "동적 경로로 도착한 PreRef는 런타임에도 명시적으로 에러" + 절 바로 아래 "PreRef는 '취소'라는 개념이 없다" 항목 참고. 7. **왜 `Compute(fn, ...)`는 여러 의존성을 편하게 받고 `Effect`/`Observer`는 안 받는가** (2026-08-11 세션 원자료, `source-state-plan.md` "`:Compute(fn, @@ -272,13 +294,16 @@ additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**: 완전히 확정(2026-08-09 열한 번째 세션엔 `Extract(index, newElement?)`로 더 확장). `research/additional-primitives-plan.md`는 더 이상 열린 항목 없음, 배경 자료로만 유지. -- **"hook"/"pre-hook" 용어 채택 여부** (2026-08-07, 위 심화 후보 6번 참고) - — `bind-system-plan.md`는 `PreRef`가 위치 무관 호이스팅이라는 것과 일반 +- **"hook"/"pre-hook"(+`PostRef` 이후로는 "post-hook") 용어 채택 여부** + (2026-08-07, 위 심화 후보 6번 참고) + — `ref-plan.md`(**[2026-08-13 아홉 번째 세션]** `bind-system-plan.md`에서 + 분리됨, 옛 경로로 적혀 있던 것을 2026-08-14 감사에서 정정)는 `PreRef`가 + 위치 무관 호이스팅이라는 것과 일반 `Ref`가 우선순위 스캔에 참여한다는 것까지는 확정해뒀고(복수 `PreRef` - 간 순서=배열 index 순서, 동적 경로로 도착한 PreRef는 전용 Handler가 - 즉시 error — 둘 다 아홉 번째 세션에서 추가 확정), **`PreRef`의 취소 + 간 순서=배열 index 순서 보장, 동적 경로로 도착한 PreRef는 전용 Handler가 + 즉시 error — 둘 다 2026-08-07 아홉 번째 세션에서 추가 확정), **`PreRef`의 취소 가능성은 2026-08-12 여섯 번째 세션에 "취소 개념 없음, 재사용은 error"로 - 해소됨**(위 심화 후보 6번, `bind-system-plan.md` 참고) — "hook 대 + 해소됨**(위 심화 후보 6번) — "hook 대 pre-hook"이라는 용어 자체를 문서화 시 채택할지만 아직 미정. `PreRef` 취소 가능성 항목은 실제로는 `.claude/question.md`에 별도로 diff --git a/CLAUDE.md b/CLAUDE.md index 3908ba5..668464f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1240,6 +1240,10 @@ PreRef pre-pass 한 스윕에서 `isPostRef`도 같이 소진해 `postRefList` 될 것 같다는 구체 스케치를 남겨 백로그 후보로 보존 — 네 번째 세션이 이 스케치를 이어받아 `ProcessedPreRef` 기반으로 갱신함(위 참고). `question.md`엔 안 올림(이미 "지금 안 함"으로 답이 나온 질문이라). +**[역전, 같은 날 아홉 번째 세션]** 이 "지금은 구현 안 함" 결정은 뒤집혔음 +— `PostRef`/`OnRendered` 둘 다 채택 확정되어 `base/ref-plan.md`/ +`base/lifecycle-hooks-plan.md`로 승격됨(아래 아홉 번째 세션 항목). +"공짜가 아니다"라는 판단 자체는 그대로 맞고, 그 비용을 지불하기로 한 것. `research/lifecycle-hooks-plan.md` 신설, README 인덱스 반영, 별도 워크트리에서 작업 후 메인에 수동 반영(다른 세션이 동시에 메인에서 작업 중이라 병합 타이밍을 사용자와 직접 조율) — 커밋 `9f9a68f`. 같은 @@ -1315,7 +1319,8 @@ pull-recompute+캐시가 막고 중복 **통지**는 안 접음(접으려면 `Bl `doc-check.py`는 참조 존재는 봐도 서술 간 모순은 못 봄. **2026-08-14 아홉 번째 세션 — `PostRef` 확정·`OnRendered` 채택, 계열 안 -fire 순서 미보장으로 역전, `lifecycle-hooks-plan.md` base 승격** +fire 순서는 미보장으로 갔다가 철회(보장 유지), `lifecycle-hooks-plan.md` +base 승격** (`session/2026-08-14-09-postref-confirmed.md`) 사용자가 백로그 후보로만 남아 있던 `PostRef`를 확정(선택지 (a) — pre-pass 공동 수집 + 두 패스 뒤 `postRefList` 소비, "Pre-Post 둘을 지원 안 할 이유가