사용자 요청("지금 나온거 이외에 더 문제될만한게 있는지 조사해봐줘 …
시스템 전체 계획을 봐도 좋고")으로 같은 파일에 2차 패스를 이어붙였다.
1차는 사용자가 지목한 5개 영역이 범위였고, 2차는 `dispatch-core-plan.md`
전문 / `source-state-plan.md` / `lifecycle-pattern.md` / `ref-plan.md` /
`tag-plan.md` / `attribute-plan.md` / `ui-shorthand-plan.md` /
`brand-plan.md` / `gate-plan.md` / `blocker-plan.md` /
`state-epoch-plan.md`와 `slot-plan.md`의 `raw*` 계층을 의사코드 단위로
훑었다.
**`base/`는 여전히 한 줄도 안 고쳤다** — 전부 발견 보고이고 회신 대기.
## 🔴 셋
- `H-11` **`Effect`의 leaf 사망 cleanup을 발화시키는 배선이 어느
의사코드에도 없다.** `lifecycle-pattern.md`는 "`Effect`가 `Destroying`
훅을 쓰는 유일한 소비자"라 못박고 `effect-plan.md`는 "leaf당 실제
Destroying 바인딩 하나"라고 비용까지 적어뒀는데, leaf가 붙는 유일한
경로인 `bindLifetime`의 실 구현 스케치는 gchold 등록 + gcconn 복사가
전부다. `Effect`가 통째로 안 돌고, 그 위의 `OnDestroyed`와
`slot._detachCleanup`(detach 요소를 파괴하는 *유일한* 경로 —
같은 문서가 "GC 폴백이 아예 없으므로 필수"라 명시)이 같이 죽는다
- `H-12` `rawRemove`/`rawUnmount`/`rawDetach`에만 "아직 마운트 전"
얼리리턴이 없다(`rawAdd`/`rawReplace`엔 있음). `Slot()` → `:Add(x)` →
`:Remove(1)`이 `nativeRemove(nil, …)`로 터진다
- `H-13` `Effect(fn, ...deps)` 역전(5라운드 `C-6`)이
`source-state-plan.md`에 반영 안 됐고, 거긴 아직 "기각"이 **일반
원칙**으로 서 있다. 반응형 코어 정본만 읽고 구현하면 단수 dep으로
되돌아가 `Ref` 의존성 갭이 재발한다
## 🟡🟢 여섯
`Effect`의 `fn` 시그니처 미확정(`self`/`previous` 자리, `Ref` dep은 lazy
핸들이 아님) / `newKeyIndex[key] = pos`가 마운트 안 된 키에도 배열
인덱스를 기록(`0` 포함) / `Dispatch.drive`의 Blocker 범위가 `F-4-1`
단일 루프와 안 맞고 `PostRef`가 게이트 안에서 발화하는 귀결이 미문서화 /
attribute 이름을 그룹 A→B로 옮기면 emit 순서에 따라 error /
`rawAdd`·`rawReplace`의 plain 분기가 `recompute`를 두 번 돌림 /
`Processed*RefHandler`의 `process` 시그니처가 핸들러 계약과 다름
## 커밋 전 `H-1`~`H-20` 전수 재검증
사용자 요청으로 커밋 전에 20건 전부를 `base/` 원문과 다시 대조했다.
- **`H-15` 철회(오탐)** — `ui-shorthand-plan.md`에 이미 사용자 판정
예외(4라운드 `UI-11`, "`retractFrom(child, prop, 1)`을 정석으로 요구하지
않는다 — 실익이 없다")가 있었는데 그걸 못 보고 `dispatch-core-plan.md`의
일반 규칙만 인용했다. 번호는 비우지 않고 철회 기록 + 재발 방지 교훈으로
남긴다(남은 건 인자 이름 `hint`가 폐기된 옛 용어라는 🟢 하나)
- **`H-2` 트레이스 정정** — 결론은 맞지만 수치가 틀렸다. `updateFn`이
반환하는 중첩 Slot은 정의상 마운트 전이고 `Length`를 쓰는 주체는
`recompute` 하나뿐이라(5라운드 `C-2`), 그 시점 `.Length`는 **항상 0**이다.
원래 적은 `+3`이 나올 수 없다. 실제 동작은 더 나쁘다 —
`isSlot` 분기가 언제나 `+0`이라 (a) 첫 아이템이면 `rawAdd(self, S, 0)`로
바로 out of bounds, (b) `pos` 전진이 원래 노리던 목적("다음 형제의
index가 물리 개수를 반영")도 첫 사이클엔 성립하지 않아 같은 데이터의
`index`가 사이클마다 달라진다
- **나머지 18건은 유효 확인.** `H-3`은 `bk.invalidAfter` 대입이 코퍼스
전체에서 `getOffsetAt` 안의 두 줄뿐임을, `H-11`은 `Destroying`이 산문에만
등장하고 어떤 의사코드에도 연결되지 않음을 각각 grep으로 재확인
- 1차의 "이상 없다고 확인한 것" 중 `Ref` 콜백 발화 계약 표현도 정정
("이미 채워져 있으면" → 미설정이어도 무조건 1회)
색인 두 곳(`README.md`의 `qa-request/` 행, `todos.md` 00번)도 같이 갱신.
`doc-check.py` ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01TPzatFCEs1CfWNPxF1pidr
사용자 요청으로 최근 확정된 5개 영역을 실제 값으로 손 트레이싱했다 —
`Effect(fn, ...deps)` / `Gate`·`Blocker` / State 전파(`rawInvalid`·emit 지연) /
Slot의 `native*`·offset·length·mount / `Brand`·`Epoch`·`EpochMap`.
4·5라운드가 "예/아니오 문항지"였던 것과 달리 2·3라운드와 같은 성격이라
파일명도 `handtrace`로 갈랐고, 발견 번호는 `H-n`.
**`base/`는 한 줄도 안 고쳤다** — 전부 발견 보고이고 사용자 회신 대기.
## 🔴 셋 (크래시하거나 조용히 어긋남)
- `H-1` `:List`의 `keyIndex`가 사이클 **도중**엔 stale인데 `rawMove`/
`rawReplace`/`releaseElement`/`rawDetach`의 live `_elements` 인덱스로
쓰인다. 앞에 하나 끼우면 순서가 조용히 뒤집히고, 리스트 비우기는 소멸
루프의 해시 순회 순서에 따라 `_elements[i] == nil`로 터진다. 5라운드가
"raw\*를 전부 index 기준으로 통일"한 근거였던 *"그 값은 이미 `keyIndex`가
들고 있다"*가 여기서 깨지므로 **그 결정의 전제를 다시 봐야 한다** — 세
갈래(라이브 인덱스 맵 / `indexOfRaw` 기본 경로화 / reconcile 2패스)를
적어뒀고 이게 유일한 사용자 판단 항목
- `H-2` `pos`가 리프 개수와 `_elements` 배열 인덱스를 겸한다. `updateFn`이
중첩 Slot(멀티루트 결과)을 반환하면 첫 사이클에 바로
`table.insert` position out of bounds
- `H-3` `getOffsetAt`의 접두합 캐시를 **아무도 무효화하지 않는다**.
`invalidAfter = min(...)` 규칙이 표로만 존재하고 `setLength`/
`gatedRecompute`/`_baseObserver`/`spliceArrays*` 어느 의사코드에도 없다.
형제 길이가 변해도 뒤 형제 offset이 고정되고(`Length`만 맞고 offset은
틀리는 형태), 포탈 재마운트는 옛 베이스가 든 캐시를 그대로 물려받는다
## 🟡🟢 일곱
`bk.invalidAfter`/`offsetCache`가 부기 스펙에 없고 초기값 `nil`이면 비교에서
터짐 / `spliceArraysUp`이 `bk.N`을 먼저 올려 스스로 금지한 창을 열고 그 안에
`nativeInsert`가 들어 있음 / `unmountSlotTree`가 미정의 `physicalTarget` 참조
(+ 순차 extract의 offset 스큐) / `Effect`의 `Ref` 의존성은 해제 API도
`canExecute` 게이팅도 없어 죽은 leaf에서 계속 발화 + 누수 /
`EffectHandle._observer`가 아직 단수라 N-deps에서 2번째 이후 재실행이 죽음 /
게이트 `withheld`가 flush 스왑에서 weak를 잃음 / `recompute`의 `sum` 시작값
주석이 stale.
## 이상 없다고 확인한 것
Epoch/EpochMap 판정 규칙(§1 다이아몬드 실제 추적), 게이트 flush 스냅샷과
`:Sync(batch)` 예외, `Effect`의 EpochMap dedup + `_installing` 순서,
`materializeSlotTree`/`mountSlotTree` 분해(depth 2 부기·물리 양쪽),
단건과 배치의 순서가 반대인 것, `Brand` 다중 태깅 — 그 문서 마지막 절이
소스이고, 다시 트레이싱할 필요 없다.
인덱스 레이어는 `.claude/README.md`(qa-request 행)와 `.claude/todos.md`
00번 갱신. `doc-check.py` ERROR 0.
Claude-Session: https://claude.ai/code/session_01TiW21rnti9SbLgF6twtn6D
Co-authored-by: qwreey <me@qwreey.moe>