quad/CLAUDE.md
qwreey 031495cc0b
qa: 9라운드 손 트레이싱 실행 + Q1~Q3 확정·반영 — 체크포인트 (Q4~Q10 다음 세션)
8라운드가 써둔 지시서(-round9-brief.md)대로 커밋 9dd8213의 델타를 재트레이싱해
발견 18건(H-124~H-141)을 냈고(qa-request/pre-implementation-handtrace-round9.md),
§4 문항 중 Q1~Q3를 사용자와 확정해 base/·ROADMAP에 반영했다. 결정의 소스는
-round9-followup.md(진행 표가 상태의 소스). 🔴 둘 다 luau 실측 재현.

Q1 H-124 — recompute가 lengthList[i]를 되감기 판정보다 먼저 읽어, offset:Set 안의
  사용자 코드가 커서 뒤 자리 수를 줄이면 sum += nil로 죽고 recomputeBlocker가
  영구 On. 되감기 판정을 앞으로(continue), 읽기·누적은 되감지 않을 때만.
Q2 H-125 — 재마운트 시 setOffsetSource가 slot.Offset을 바꾸는 순간 _baseObserver가
  unbind 상태라 두 필드가 0으로 안 내려가 옛 베이스의 offsetCache[1]을 씀.
  사용자 확정: Offset·_baseObserver를 Slot 생성자로(첫 마운트/재마운트 분기
  소멸), materializeSlotTree는 blocker:On → bindLifetime → setOffsetSource,
  파괴는 _destroyed 플래그 하나(핸들은 unbind만, mutate CRUD·:List·마운트 진입
  error, Owned=false는 안 섬, 이중 dispose no-op).
Q3 H-126/H-137/H-141 — element→index 맵이 Slot 층(slot._elemIndex)과 Dispatch
  층(bk.tokens/indexOfToken)에 두 벌 있었고, 후자의 token은 사용자가 정한 적
  없는 것(2026-08-25 /code-review가 발명해 사용자 인용문 옆에 앉아 있던 것).
  bk.indexOfElement 하나로 통일, setLength 5번째 인자 element, splice가 비운
  자리는 세 배열 전부 처리. H-137 소멸.

부수: H-140(ROADMAP의 폐기된 "해제 시 slot.Offset = nil" 잔존) 정정,
H-125 피해 범위를 "중첩 Slot의 Offset"으로 축소(유저 체인은 요소 인스턴스에
바인드돼 전파됨 — 실측), G각도로 "for d in seen do는 유효한 Luau가 아니다"가
거짓임을 확인, keyof<{}> 빈 Store 실측 클린.

conventions.md 신설: "/code-review(그리고 메인 세션)가 내놓는 새 필드·인자·
이름·메커니즘은 발견이지 결정이 아니다" — 이 세션에서 메인 세션도 같은 실수를
세 번 했다(subject 인자 / Observer 위치 필드 = 기각된 Effect userdata 재개방 /
조회 클로저).

검증: quad-doc-auditor 6라운드(확실 1→1→3→1→1→0, base/ 본문 결함은 1라운드
이후 0건 — 나머지는 인덱스 레이어·인용처), doc-check ERROR 0. /code-review high는
Q4~Q10 반영 뒤 한 번에(같은 파일을 또 건드려 diff가 섞이므로 지금 체크포인트).

Claude-Session: https://claude.ai/code/session_01F9zgJ4c4kDitAoQMm9qxKn
Co-authored-by: qwreey <me@qwreey.moe>
2026-08-27 01:30:48 +09:00

58 lines
3.7 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-26] 8라운드 손 트레이싱까지
처리 완료 — M2 착수를 막는 항목이 하나도 없다.**
결정의 소스는
`.claude/qa-request/pre-implementation-handtrace-round8-followup.md`
(7라운드 몫은 `-round7-followup.md`; **[2026-08-27] 9라운드 몫은
`-round9-followup.md` — Q1~Q3 반영 완료, Q4~Q10 대기**)이고 `.claude/question.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`.
후자들은 과거 시점 서술이라 더 최근 결정이 안 반영돼 있을 수 있음.