quad/CLAUDE.md
qwreey eb321e1747
qa: 7라운드 발견 52건 처리·반영 — Store 재설계 시도와 철회, 타입 함수 원칙 신설
`qa-request/pre-implementation-handtrace-round7.md`의 `H-55`~`H-106`을
사용자와 대화형으로 처리하고 `base/`에 전량 반영했다. 결정의 소스는
`-followup.md`(개수·개별 항목은 여기서 세지 않는다).

## 처분

확정 39 / 무효·소멸 4(`H-73`~`H-76`) / 기각 1(`H-77`) / 범위 축소 2 /
다른 항목으로 흡수 6.

**부수로 `question.md` 최우선 절이 비었다** — 중간 State GC는 `_hold`
불변식(하류 → 상류 강함)으로, 동적 키 표면 위치는 `store:Of<<T>>(name)`
하나로 닫혔다. **M2 착수를 막는 항목이 없다.**

## 구조가 바뀐 것

- `Ref`가 `Epoch`를 만족(`.Revision` + `EpochBrand`) — 포탈 캐치업 비대칭과
  같은 `Ref` 중복 dep이 같이 닫힘
- `Weak*` 등록 표면 신설(`Ref:WeakCallback` / `Observer:WeakSubscribe`) —
  Weak 쪽이 프리미티브고 강한 쪽이 "GC 킵"을 얹은 것
- `Effect`: dep 등록이 생성자 한 곳으로, 강한 주인은 `_deps` 하나,
  억제는 사적 `Blocker`, `bindLifetime`은 핸들 하나에만 적용.
  `:Rerun()` 정의 신설(재진입은 지연 재실행), `_installed` 신설
- 전파 루프 의사코드 확정 — 구독자 집합의 원소는 Observer **값**이고
  **자식 State 노드는 `canExecute`를 안 탄다**(그대로 짜면 파생 State
  아래가 전부 침묵했다)
- `rawInvalid` → `cacheTargetCount`/`cacheCurrCount` 카운터 쌍
- `recompute` 재진입 차단 + `invalidAfter` 되감기, `gatedRecompute`가
  인덱스를 캡처하지 않고 토큰으로 조회
- `emit(commit) -> boolean`, `EpochMap:Peek` — 정책이 흡수 집합을
  버리지도 읽지도 못하던 것을 닫음
- error 계약(`level` 이분, 메시지는 영어)과 예외 계약(`pcall`로 안 감쌈)을
  `architecture.md`에 신설

## Store — 같은 날 재설계하고 철회했다

`H-75`/`H-76`으로 `WrapStore`/`ProcessStoreType`이 폐기되자 그 자리를
"`store.key`는 값, `store:Of(k)`가 프리미티브"로 채웠다가 **같은 날
철회**했다(`archive/store-value-field-redesign-withdrawn.md`). 살아남은 건
**명시적 초기화** 하나다. 최종형은 타입 인자에 `Source<T>`를 직접 쓰고
`store.key`는 평범한 레코드 필드이며 **타입 함수를 안 쓴다**.

철회 이유 중 하나가 원칙으로 승격됐다 — **"타입 함수는 타입이 못 잡는
문제를 에러로 띄우는 정도 이상으로 가지 않는다"**(`typing-limits.md` §0).
`index<>`/`keyof<>`도 Luau가 predefine한 타입 함수라 같은 함정을 갖는다.

## 툴체인

**`luau` CLI가 심볼릭 링크를 못 탄다**(디렉토리·파일 둘 다)는 것이 최소
재현으로 밝혀졌다 — pesde 워크스페이스 링크가 전부 심볼릭이라 스모크 2개가
안 돌았고 `luau-analyze`는 **조용히 통과**했다("거짓 클린"). `scripts/relink.sh`
+ `scripts/test.sh` 신설, 이제 스모크 셋 전부 PASS.

## 검증

`/code-review high` 2회(12건 + 14건)와 감사 8패스(12/6/6/4/13/3/6/0건)를
각도를 바꿔가며 돌렸고 전부 반영했다 — 마지막 패스가 무발견으로 수렴.
각 패스의 각도와 대표 발견은 `-followup.md`의 검증 절이 소스.
`doc-check.py` ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>

Claude-Session: https://claude.ai/code/session_012oLwATeQdq9TCFdENPutFG
2026-08-25 23:40:19 +09:00

55 lines
3.6 KiB
Markdown

# CLAUDE.md
Roblox 엔진용 DOMless UI 렌더러 **quad**를 처음부터 다시 짜는 프로젝트.
**[2026-08-24 기준] M0(스파이크 검증)/M1(스캐폴딩)까지 완료, 다음은
M2(반응형 코어 — Source/State/Store)**. **⚠️ [2026-08-24] M2와 M3의
번호·순서가 맞바뀌었다** — 예전엔 M2=디스패치, M3=반응형이었는데 의존이
한 방향(디스패치 → 반응형)이라 반응형을 먼저 짓기로 확정했다. 그래서
**2026-08-24 이전에 쓰인 `session/`·`archive/`·`qa-request/` 문서의
`M2`/`M3`는 옛 의미로 읽을 것**(라이브 문서는 전부 새 번호로 맞춰뒀음).
경위는 `.claude/archive/question-resolved.md`의 "마일스톤 경계" 절,
새 구성은 `ROADMAP.md`의 M2 배너. **⭐ [2026-08-25] M2 착수를 막는 항목은
없다** — `.claude/question.md`의 최우선 절이 **비었다**(7라운드 손 트레이싱
후속에서 중간 State GC와 `store:GetDynamic` 위치가 같이 닫혔다, 결정 전량의
소스는 `.claude/qa-request/pre-implementation-handtrace-round7-followup.md`). 같은 상태를 `.claude/project-context.md`
서술하니 마일스톤이 넘어갈 때 두 곳을 같이 고칠 것. 진행 상황의 소스는
항상 루트 `ROADMAP.md`.
<!-- [2026-08-16 재구조화] 이 파일은 1537줄까지 불어나 (a) 사람이 검토 불가,
(b) 공식 권장치(파일당 200줄) 7.7배 초과로 지침 준수도 저하, (c) 에이전트가
긴 파일을 편집할 때 실수 증가를 유발했음. 주제별로 쪼개고 @import로 다시
합침 — import는 컨텍스트를 줄여주지 않지만(전부 그대로 로드됨) 사람 검토성과
편집 정확도, 그리고 파일 단위 자동생성 가능성을 산다.
※ 이 주석은 컨텍스트 주입 전에 제거되므로 사람용 메모만 넣을 것. -->
**이 파일에 내용을 직접 쌓지 말 것** — 짧은 진입점으로 유지한다. 새
서술은 아래 import된 파일 중 맞는 곳에 넣을 것(어디에도 안 맞으면 그건
매 세션 로드될 내용이 아닐 가능성이 높음 — `.claude/` 아래 해당 문서로).
## 항상 로드되는 컨텍스트
관례와 작업 방식 @.claude/conventions.md
프로젝트 컨텍스트와 문서 구조 @.claude/project-context.md
지금 할 일 @.claude/todos.md
## 온디맨드 자료 (자동 로드 안 됨 — 필요할 때 직접 열 것)
| 무엇이 궁금할 때 | 어디를 볼 것 |
|---|---|
| **지금 유효한 설계** — 어떤 결정이 확정돼 있는가 | `.claude/base/` (먼저 `.claude/base/architecture.md`) |
| 문서 전체 색인 — 어느 파일이 뭘 다루는가 | `.claude/README.md` |
| 사용자가 답해야 할 열린 질문 | `.claude/question.md` |
| 구현 순서 / 마일스톤 | 루트 `ROADMAP.md` |
| 사람만 할 수 있는 일 | 루트 `HUMAN_TODO.md` |
| **어떤 결정이 왜 그렇게 됐나 / 전에 뒤집힌 적 있나** | `.claude/session-summary.md`를 grep (세션별 2~4줄 요약 색인) |
| 그 결정의 논쟁 과정 원문 | `.claude/session/YYYY-MM-DD-NN-slug.md` |
| 뒤집히거나 기각된 설계의 원문 | `.claude/archive/` |
**주의**: `session-summary.md`는 의도적으로 `@import` 안 함 — 계속 자라는
히스토리 문서라 통째로 올릴 이유가 없음(그 문서 스스로 "항상 읽을 필요 없음,
지금 유효한 설계는 `base/`가 소스"라고 명시). 필요해지면 그때 가서 열 것.
**충돌 시 우선순위**: `.claude/base/` > 여기 요약이나 `session-summary.md`.
후자들은 과거 시점 서술이라 더 최근 결정이 안 반영돼 있을 수 있음.