qa: 구현 전 QA 4라운드 문항지 — base/ 전 문서 확정 주장 전수 문항화
사용자 요청("모든 확정 부분에 있어서 예가 되어야하는 질문들을 계속")으로
base/ 26개 문서를 서브에이전트 없이 한 맥락에서 의존성 순서로 읽으며,
확정으로 적힌 주장을 전부 "예가 나와야 정상인 단정문"으로 뽑았다.
결론만이 아니라 근거까지 문항에 넣었는데, 코퍼스에 "결론은 그대로인데
근거가 뒤집힌" 사례가 여럿 있어서 결론만 물으면 그런 걸 못 잡기 때문.
⚠️로 열려 있다고 적힌 항목은 "확정이 맞나"가 아니라 "아직 열려 있다는
인식이 맞나"를 묻는 문항으로 따로 표시했다.
정정은 하나도 안 했다 — 사용자 지시대로 기록만 하고 회신 대기 상태.
Co-authored-by: qwreey <me@qwreey.moe>
This commit is contained in:
parent
e290499fce
commit
e80aa11c4f
5 changed files with 3407 additions and 3 deletions
|
|
@ -24,7 +24,7 @@
|
|||
| `base/` | 결정 완료 + 프로젝트 전체에 걸치는 컨텍스트 — plan/done 개념 없음, 계속 참조되는 배경지식. **항상 읽어야 하는** 배경지식만 여기 둠(다른 문서를 이해하는 데 전제되는 것) |
|
||||
| `reference/` | **[2026-08-07 신설]** 결정 자체가 아니라 다른 문서가 근거로 인용하는 온디맨드 참고 자료(v1 스냅샷, 프레임워크 비교 리서치) — "완료" 개념 없는 건 `base/`와 같지만, 항상 읽을 필요는 없고 해당 문서가 인용될 때만 열어보면 됨. `quadnomicon` 소재 후보가 많음 |
|
||||
| `research/` | 아직 착수 전, 사용자와 스코프/설계를 더 상의해야 함 |
|
||||
| `qa-request/` | 원래 용도는 "구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음". **[2026-08-18 확장]** 구현 전에도 **사용자 심사 라운드의 산출물**을 여기 둠 — `pre-implementation-qa-round1.md`(1라운드: `base/` 확정 문서 전체를 문항으로 재확인받아 **"아니오"가 나온 항목만** 모은 결함 목록 + 신규 요구사항(`N-n`) + 부수 오탈자. **같은 날 전부 `base/`에 반영 완료**라 지금은 "무엇이 왜 틀렸었나"의 근거 기록이고, 지금 유효한 설계는 항상 `base/`가 소스. 아직 안 닫힌 것은 `question.md` 3번과 `.claude/todos.md` 00번이 소스), `pre-implementation-qa-round2.md`(2라운드: 확정 의사코드를 실제로 손으로 실행해보는 트레이싱 — **완료**, 발견된 크래시 `RC-1`(`recompute` 트리거 모델)도 같은 날 후속 세션에서 Blocker 게이팅 설계로 해결·반영까지 끝남, `archive/question-resolved.md`에 논의 요지 정리), `pre-implementation-qa-round3.md`(3라운드: `RC-1` 해법(Blocker 게이팅)이 실제로 `attachSlot`/`recompute`에 반영된 걸 손으로 트레이싱 — **완료**, `RC-3`/`RC-4`(`activateList`가 자기 Slot의 Blocker보다 먼저 실행되는 순서 문제)와 `bk.N` 수명주기 미정을 발견했다가 같은 세션에 사용자가 최초 분석 오류를 직접 정정하며 전부 해결·`base/` 반영까지 끝남, `archive/question-resolved.md`에 논의 요지 정리. `ROADMAP.md` M2가 M3의 `Blocker.luau`에 의존하게 된 마일스톤 순서 불일치는 각주로 반영, 마일스톤 재편 여부는 열려 있음). 다음 라운드가 필요해지면 라운드마다 파일을 새로 만들고 이름에 라운드 번호를 넣을 것 |
|
||||
| `qa-request/` | 원래 용도는 "구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음". **[2026-08-18 확장]** 구현 전에도 **사용자 심사 라운드의 산출물**을 여기 둠 — `pre-implementation-qa-round1.md`(1라운드: `base/` 확정 문서 전체를 문항으로 재확인받아 **"아니오"가 나온 항목만** 모은 결함 목록 + 신규 요구사항(`N-n`) + 부수 오탈자. **같은 날 전부 `base/`에 반영 완료**라 지금은 "무엇이 왜 틀렸었나"의 근거 기록이고, 지금 유효한 설계는 항상 `base/`가 소스. 아직 안 닫힌 것은 `question.md` 3번과 `.claude/todos.md` 00번이 소스), `pre-implementation-qa-round2.md`(2라운드: 확정 의사코드를 실제로 손으로 실행해보는 트레이싱 — **완료**, 발견된 크래시 `RC-1`(`recompute` 트리거 모델)도 같은 날 후속 세션에서 Blocker 게이팅 설계로 해결·반영까지 끝남, `archive/question-resolved.md`에 논의 요지 정리), `pre-implementation-qa-round3.md`(3라운드: `RC-1` 해법(Blocker 게이팅)이 실제로 `attachSlot`/`recompute`에 반영된 걸 손으로 트레이싱 — **완료**, `RC-3`/`RC-4`(`activateList`가 자기 Slot의 Blocker보다 먼저 실행되는 순서 문제)와 `bk.N` 수명주기 미정을 발견했다가 같은 세션에 사용자가 최초 분석 오류를 직접 정정하며 전부 해결·`base/` 반영까지 끝남, `archive/question-resolved.md`에 논의 요지 정리. `ROADMAP.md` M2가 M3의 `Blocker.luau`에 의존하게 된 마일스톤 순서 불일치는 각주로 반영, 마일스톤 재편 여부는 열려 있음). `pre-implementation-qa-round4.md`(4라운드: 사용자 요청으로 **`base/` 확정 전체를 "예가 나와야 정상인 문항"으로 다시 뽑은 전수 문항지** — 1~3라운드와 달리 **작성만 끝났고 사용자 회신 대기 중**, 회신 후 "아니오"만 남긴 근거 기록으로 재편 예정. 문항 수/문서별 분포는 그 파일 자신이 소스). 다음 라운드가 필요해지면 라운드마다 파일을 새로 만들고 이름에 라운드 번호를 넣을 것 |
|
||||
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). **[2026-08-06 확장]** 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용(제목 `[역전됨]` — 한 번 확정했다가 뒤집힌 것) — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 `base/`/`research/`에서 뺌) `quadnomicon` 소재로는 나중에 쓸 수 있음. **[2026-08-07 확장]** 후보였다가 채택 안 된 것(확정한 적 없이 검토 후 기각)도 같은 방식으로 보존, 제목은 구분을 위해 `[기각됨]` — `[역전됨]`과 의미가 다르므로 혼동하지 말 것. **[2026-08-07 세 번째 확장]** 설계 반전/기각과 별개로, 에이전트가 문서 작성 중 스스로 낸 개념 혼동을 정정한 이력은 `[에이전트 실수]` 태그로 `agent-mistake.md` 하나에 모음(`.claude/session-summary.md`/`session/` 로그와의 중복 방지) |
|
||||
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — **[2026-08-19 기준] 폴더 자체가 아직 없음**(M0/M1 스캐폴딩만으론 안 생기고 실제로 렌더링해보고 쓰는 단계부터, 첫 피드백이 생길 때 만들면 됨). `qa-request/`는 **[2026-08-18] 더 이상 비어 있지 않음**(구현 전 QA 1라운드 산출물이 들어감) — 여긴 아직 폴더도 없음 |
|
||||
| `luau-test/` | **[2026-08-09 신설]** `base/` 확정 사항 중 "추론만으로 확정하고 실제 Luau로 부딪혀본 적 없는 것"(M0 스파이크 대상)을 `luau`/`luau-analyze`/`luau-lsp`/Roblox Studio로 사용자가 직접 돌려볼 독립 실행 스크립트 모음. **[2026-08-13 여섯 번째 세션, 첫 실측]** `luau`/`luau-analyze` 바이너리가 생겨 처음으로 실제 실행 — **런타임 12개 전원 통과**, 타입 쪽에서 `:Compute(fn)` lazy 핸들 계약이 Luau 추론과 충돌하는 게 드러남(당시 `question.md` 0-Y). **[2026-08-13 열세 번째 세션]** 그 0-Y가 해소되며 `review-required/`가 **비었음** — 계약은 유지 확정, 남은 건 Luau 자체 한계라 `base/typing-limits.md`가 담당. **`STATUS.md`가 상태의 소스**(pass / 사람 결정 필요 / 스파이크 깨짐 / 미실행 분류 — 사람이 먼저 볼 것만 위에), `luau-test/README.md`는 각 파일의 검증 의도·배경, 실행 결과 상세는 `audit/luau-test-first-run-2026-08-13.md` |
|
||||
|
|
|
|||
288
.claude/qa-request/pre-implementation-qa-round4-response.md
Normal file
288
.claude/qa-request/pre-implementation-qa-round4-response.md
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
### D-3 — retractor는 자기 자원만 정리
|
||||
retractor는 자기 하위 위임까지 쫓아가 정리할 필요가 없다 —
|
||||
`Dispatch.retractFrom`이 **항상 깊은 인덱스부터 얕은 쪽으로** 정리하므로 이
|
||||
클로저가 불릴 시점엔 자기 아래는 이미 정리된 뒤다. → **예/아니오**
|
||||
-> 방향이 그게 맞나? 방향을 더 서술해주길 바람. 실제 process 에선 retract 가 인덱스 1 부터 5, 6, ... 순으로 작동함. 그런데 달라질 때 5, 4, 3, 2 ... 순이 되는건 아니지? '깊은 인덱스' 라는게 무슨 의미?
|
||||
|
||||
### D-8 — `Quad.debug` 플래그
|
||||
동률 경고 print는 모듈 표면의 불리언 `Quad.debug`(기본 `false`)가 참일 때만
|
||||
찍는다. `Quad.debug`는 새 공개 API 표면이라 `module-lifecycle-plan.md`에도
|
||||
반영이 필요하고, 다중 인스턴스화 시 인스턴스별인지 전역인지는 그때 정한다.
|
||||
`Dispatch.listHandlers()`가 이 플래그와 무관하게 항상 호출 가능한지도 구현 시
|
||||
정한다. → **예/아니오**
|
||||
-> listHandlers 는 항상 실행 가능. 유저가 필요하면 수행 시 목록들을 단순 반환해주고 출력하고 싶다면 출력하는 용도임.
|
||||
|
||||
### D-10 — 두 패스 순회 계약
|
||||
`Dispatch.drive`는 Lua 테이블의 우연한 순회 순서에 기대지 않고 **명시적으로 두
|
||||
패스**(배열 파트 먼저, 해시 파트 나중)로 돈다. 이유는 (1) 다른 백엔드의
|
||||
이식성, (2) 어차피 구분 비용이 드니 순서 고정이 거의 공짜. **M0 스파이크에서
|
||||
실제 Luau로 순회 동작을 검증**할 항목이다. → **예/아니오**
|
||||
-> 루아우의 일반화된 반복 for 이 이를 그냥 지원함. 어떻게 실제로 이해했는지 확인 필요
|
||||
|
||||
### D-32 — 재위임 핸들러의 (A) 분기 의무
|
||||
재위임하는 핸들러는 (A) 분기에서도 **반드시 다시 재위임**해야 한다. 조건부로만
|
||||
재위임하는 핸들러를 만들면 건너뛰는 자리에서 `Dispatch.retractFrom(inst,k,index+1)`을
|
||||
직접 불러야 한다. → **예/아니오**
|
||||
-> 무슨말인지 확인 필요
|
||||
|
||||
### D-56 — `setLength`의 생명주기 경로
|
||||
`setLength`가 만드는 Observer는 `:Subscribe()`가 아니라 `bindLifetime(ownerKey,
|
||||
observer)`로 묶인다 — `ownerKey`가 죽을 때 같이 죽어야 하는 내부 배관이기 때문.
|
||||
`setLength` 자신은 `recompute`를 직접 부르지 않고 항상 `gatedRecompute`를
|
||||
경유하며, Observer의 "등록 즉시 1회 실행"으로 촉발되는 최초 호출도 예외 없이 이
|
||||
게이트를 통과한다. → **예/아니오**
|
||||
-> 맞음. 그런데 ownerKey 가 Slot일 수도 있음. 각 엔진의 bindLifetime 은 이를 잘 핸들링 해줘야함. 즉, Slot안에, 또는 바깥에 SetStrong 으로 gchold 비슷한걸 수행하면 됨.
|
||||
|
||||
### D-58 — 마운트보다 offset 갱신이 먼저
|
||||
`rawAdd`는 `self.Length:Set(newCount)`(다운스트림 offset/LayoutOrder 갱신이 여기서
|
||||
동기적으로 끝남) → `element.Parent = target` 순서로 호출해야 한다 — 안 그러면
|
||||
Roblox의 실시간 `UIListLayout` reflow에서 한 프레임 순서가 깨진 채 노출된다.
|
||||
-> 추가적으로 보면, Length 설정도 마운트 전임. 액티베이션으로 length 가 결정되었을 때 그걸 넣어주고 나서 최종 마운트를 함.
|
||||
|
||||
### D-60 — `Slot.Length`와 `Slot.Offset`은 별개
|
||||
`Length`는 Slot이 스스로 노출하는 출력값(지금 실제 마운트된 개수, "n개 검색됨"
|
||||
UI에 그대로 써도 됨)이고 `Offset`은 Dispatch가 등록받아 `recompute`가 채우는
|
||||
입력값이다 — 서로 다른 두 `Source<number>`다. `Offset`은 마운트 전엔 `nil`이다.
|
||||
-> 마운트 전에는 0 이긴 함. 다만 list 의 관측으로 실체화된 값이 나오는게 offset 설정 이후라서 그 땐 0 이 아닐 수 있을 뿐
|
||||
|
||||
### LP-1 — `Connected`는 계산 속성
|
||||
`Connected`는 저장되는 bool이 아니라 "내가 아직 살아있게 하는 뒷받침 참조가
|
||||
nil인지"를 확인하는 **계산된 속성**이고, 해제는 그 참조를 `nil`로 만드는
|
||||
것뿐이다(자료구조를 즉시 재구성하지 않음). → **예/아니오**
|
||||
-> 무슨 말인지 모르겠음. Connected 는 단순히 RBXScriptConnect 안의 속성이고, Destroy 수행 시 모든 커넥션이 죽으니 자연스럽게 Connected 가 false 이 되는것 뿐임. nil로 참조를 만들 이유도 없음. gc 되어 아에 gcconn 이 없거나, 아직 gc 는 안 되었는데 Destroy 직후라 false 이거나 한것 뿐임.
|
||||
|
||||
### LP-2 — Instance 파괴 관측 지점
|
||||
Instance 파괴 관측 지점은 `Destroying` **하나로 통일**하고 `AncestryChanged`나
|
||||
폴링은 안 쓴다. 다만 실제로는 이 훅을 쓰는 지점이 예상보다 적을 가능성이 크다.
|
||||
-> 당장은 Effect 뿐임.
|
||||
|
||||
### LP-4 — 엔진 레벨 보강
|
||||
Roblox 엔진 자체가 Destroy 시 Tag/Attribute/실행 중인 Tween을 정리해주므로
|
||||
라이브러리가 따로 처리할 필요가 없고, 커스텀 Destroy-time 처리가 필요한
|
||||
사용자는 `[Event "Destroying"]`을 직접 바인드하면 된다.
|
||||
-> Effect 임. 그리고 그 슈거인 OnDestroyed 존재
|
||||
|
||||
### BR-7 — duck-typing 기각 근거
|
||||
duck-typing을 안 쓰는 이유는 (a) 우연히 비슷한 모양의 값에 false positive,
|
||||
(b) 일부 Roblox userdata는 정의 안 된 키 인덱싱 자체에서 에러를 던져 `pcall`로
|
||||
감싸야 하거나 최악의 경우 엔진이 죽는 상황까지 생김 — 둘 다다. → **예/아니오**
|
||||
-> 더 설명좀 필요
|
||||
|
||||
### M-2 — flatten의 판별 수단
|
||||
flatten은 배열을 훑으며 `isModifier(v)`가 참인 항목만 필드를 뽑아 merge하고
|
||||
나머지는 전혀 안 건드리고 배열 파트에 그대로 남긴다(그래서 `None`은 flatten을
|
||||
그냥 통과한다). → **예/아니오**
|
||||
-> 이것도 이 이후 ProcessedModifier / ProcessedModifierHandler 를 만들면 될듯.
|
||||
Post/Pre Ref 와 유사히 처리 가능하다고 생각함.
|
||||
|
||||
### M-5 — `None`은 raw 저장 계층에만 있는 실재 센티널
|
||||
`{ TextColor3 = None, mod }`도 `mod:TextColor3(None)`도 둘 다 지원되고, Modifier
|
||||
setter/`Overridden`/인라인 props는 `None`을 그냥 평범한 raw 값으로 저장·교체할
|
||||
뿐 특별 취급이 전혀 없다. 실제 "지우기"는 디스패치 단계의 `NoneHandler`가
|
||||
담당한다.
|
||||
-> 예, 다만 mod:TextColor3(nil) mod 그룹에서 제거된걸 생성하게됨. None 이 오직 명시적 'unsetter' 임. 이미 그렇게 구현되겠지만, 문서화에 유의를 두지 않았다면 확인해두어야할 부분.
|
||||
|
||||
### M-9 — Setter는 리터럴과 변환 함수 둘 다
|
||||
`:FontSize(value)`와 `:FontSize(function(current) ... end)` 둘 다 지원하고,
|
||||
**Getter는 안 만든다**(변환 함수 하나가 getter가 필요했던 유일한 케이스를
|
||||
인라인으로 커버). `old`는 항상 "현재 저장된 그대로" 넘어간다(plain이면 raw 값,
|
||||
State면 State 핸들 그 자체).
|
||||
-> 사실 Peek 가 게터라고 봐도 되긴 함, 그래서 애매한 질문이였음.
|
||||
|
||||
### BK-9 — `HasBlocked` 신설 안 함
|
||||
`IsBlocked`/`HasBlockedEmit` 필드는 그대로 유지하고 Blocker 자신의 새 최상위
|
||||
플래그(`HasBlocked`)는 **신설하지 않는다** — `OffWithoutEmit()`이 각 gated
|
||||
state의 기존 `HasBlockedEmit`을 리셋해주는 것으로 충분하다.
|
||||
-> 있는게 어렵지 않다고 보긴 하나, 사용 케이스가 없었을 뿐임. 단순 백로깅 상태로 두어도 되나, 당장 개발에 필요 없음. 나중에 사용 필요 요구가 나오면 그 때 구현하여도 될 요소로 보임. 아마 HasBlockedState 로 하나가 신설될 가능성이 존재하지 않는다고 못 박기는 이름.
|
||||
|
||||
### E-10 — ⚠️ 미해결 항목 확인: dedup 경로의 대칭
|
||||
그 dedup 경로에서 retract가 아무것도 안 한 뒤 `process` 쪽도 정말 아무것도 안
|
||||
하는지 대칭이 실제로 성립하는지는 **아직 확인 안 된 항목**이고, 특히
|
||||
`EffectHandle`의 내부 Observer cascade가 dedup 분기 안에 제대로 들어가 있는지는
|
||||
별도 확인 대상이다(M3 착수 전).
|
||||
-> 확인해봐야한다 생각함. 이로 인해 relate 로 effect 핸들러 쪽에서 old 값을 직접 들고 있어야 하고 dedup 이면 retract 에서 old 를 안 지워주고 process 로 조회해보고 같으면 deup 되어야하는듯.
|
||||
|
||||
### E-11 — `:Subscribe()`한 핸들의 `:Unsubscribe()` 의미 확장
|
||||
Observer의 `:Unsubscribe()`는 "미래 재실행만 끊는다"로 충분하지만, Effect의
|
||||
계약은 "생애주기가 끝나는 시점에 마지막 cleanup이 정확히 1회"이므로
|
||||
`:Unsubscribe()`도 "지금 끝났다"는 신호로 취급해 (1) 내부 Observer 구독을 끊고
|
||||
(2) 직전 cleanup을 정확히 1회 호출하며 (3) 이후 leaf가 실제로 죽어도 중복
|
||||
호출되지 않는다. → **예/아니오**
|
||||
-> 뭔가 애매함. 옵저버에선 leap 바인딩에 Unsubscribe 못 하는것 처럼, Effect 또한 리프 바인딩에 있어서는 Unsubscribe 안 먹어야 하는거 아님? State<Effect> 오고 emit 당함 process 다시 나는데 문제가 안 되는거임? '(3) 이후 leaf가 실제로 죽어도 중복 호출되지 않는다.' 가 좀 이상해보임.
|
||||
|
||||
### R-11 — 분기와 소진
|
||||
`type(v) == "thread"`면 대기자로 보고 resume 후 `[i] = nil`로 소진,
|
||||
`"function"`이면 콜백으로 보고 호출만 하고 소진 안 함, `nil`이면 빈 슬롯이라
|
||||
스킵한다. 새 등록은 `table.insert`가 아니라 **빈 슬롯을 선형 탐색해 재사용**한다.
|
||||
-> 아니요. table.insert 자체가 가장 처음 nil 이 등장하는 인덱스에 넣어주기에 table.insert 가 맞음. 단순 for 문에서는 해시 슬롯이든 어레이 슬롯이든 상관 없이 전부 for 가능해서, 여기선 callback 들 순서가 큰 상관 없어서 중간에 nil이 허용됨. 그리고 그 중간중간에 table.insert 가 잘 넣어주는게 맞음. None 으로 바꾸면 무한정 불어나지만, nil이면 그렇지 않음.
|
||||
|
||||
### SL-4 — 핸들러 계층 값 금지의 근거
|
||||
`Dispatch/Leaf.luau`가 처리하는 leaf 케이스는 **그 컴포넌트가 지금 만들고 있는
|
||||
Instance 자기 자신을 가리키는 self-ref 캡처**라 `inst`가 고정돼야 의미가
|
||||
성립하는데, **Slot은 이미 존재하는 부모에 나중에 독립적으로 붙는 동적 리스트라
|
||||
그 전제 자체가 없다** — Slot 안의 Ref가 무엇을 가리켜야 하는지 정의가 안 된다.
|
||||
대체 경로(`slot:Add(Frame { Ref = myRef })`, 여기서 `Frame`은 `Ref`라는 named
|
||||
파라미터를 받는 **컴포넌트 함수**)가 있어 능력 손실도 없다.
|
||||
-> 맞고, 틀린건 없는데 Frame {} 로 두면 리프 노드로 보일 가능성이 있어보임. 실 문서상 그렇게 있다면 MyComponent 정도로 바꿔주는게 혼선의 여지가 없음.
|
||||
|
||||
### SL-5 — `isMounted` 이중 추적 분리
|
||||
Slot 컨테이너 자신은 `self._mounted` 필드 하나로, 개별 element는 전역 멤버십
|
||||
(`elementOwner`)으로 추적한다. `self._mounted`의 트리거 시점은 **Instance
|
||||
`Parent` 대입 완료가 아니라 `Dispatch.process`가 이 Slot에 대해 실제로 호출된
|
||||
순간**이다 — 다른 모든 "마운트됨" 판정이 dispatch-process 시점 기준이라 여기만
|
||||
post-effect 기준이면 일관성이 깨진다.
|
||||
-> 더 풀어 서술해주길 바람. 판단 보류
|
||||
|
||||
### SL-40, SL-43 SL-45
|
||||
slot 에서 키가 사라지면 nil 로 updateFn 호출되는거 맞지? 근데 데이터 자체가 nil 일수도 있다는 생각. Detach 와 유사한 'KeyGone' 등의 상태지정 enum/싱글톤을 제공해버리고, T|KeyGone 하는게 안전해보인다는 생각.
|
||||
'`updateFn`이 새 값을 반환하면 밀려난 `prev`는 **언마운트만**' 는 이상한듯. 새 값으로 밀려난 prev 는 dispose 되는게 맞음. updateFn 은 직접 destroy 를 호출 못함(reconcile 중 빼는게 안 되니까)
|
||||
그래서 지울 방법이 존재하지 않고, 지워주는게 맞다고 봄.
|
||||
Detach 홀드 중 키 소멸은 KeyGone으로 처리되면 해결될듯. updateFn 은 위에서 if v == KeyGone then ... 처리하는게 있는게 나아보임.
|
||||
소멸 루프가 `keyIndex`를 순회하는 것 - 또한, 단순히 KeyGone 을 주고, userdata 를 지울지 말지는 유저가 결정하게 위임해버리는게 가장 깔끔하다고 보이는 부분임. 이것으로 SL-45 도 닫아짐.
|
||||
|
||||
### SL-48 — Destroy 이후가 공짜로 해결되는 이유
|
||||
`inst`가 Destroy되면 gcconn이 죽어 `canExecute`가 거짓이 되고, `gchold`가
|
||||
`Relate(inst)` 아래 있어 그 안에 붙잡힌 Observer/클로저(`mounted`/`userdata`/
|
||||
`keyIndex` 포함)가 전부 GC 대상이 된다 — 명시적으로 구독을 끊는 새 코드가 필요
|
||||
없다.
|
||||
-> 더 나아가 slot in slot 에서도 유효한가 생각해보아야함. 아마 그런것으로 알고있음. 피지컬 홀더랑 오너가 다르거든.
|
||||
|
||||
### SL-51 — `:Single`은 `:List` 위의 순수 sugar
|
||||
`:Single`은 `:List`를 0/1개짜리 배열로 감싸는 sugar이고, **key를 고정값으로
|
||||
두는 게 핵심**이다(값 자체를 key로 쓰면 매번 다른 item 취급돼 파괴+재생성이
|
||||
강제됨). `index`를 안 넘기는 이유는 형제가 자기 하나뿐이라 항상 상수라서다.
|
||||
-> 주의할 점이 보임. state<Frame> -> slot {frame} 형태가 될 때 이전 state 에서 변경으로 다른게 와도, slot 이 이전 frame 을 destroy 해버리면 안 됨. 뽑는게 안되면 안된다는건데, list 슈거에서 Detach 가 사용중인지 확인이 필요해보임. 또, Slot 안에 State 가 오면 이를 Slot으로 감싸주는게 유효할텐데, 거기서 안전한지도 봐야함(SL-66 에서 사용되는걸로 보이긴 하나, 안전 유무는 별도의 문제로 보임)
|
||||
|
||||
### SL-58 — 배치 밖 단독 재마운트의 엣지 케이스
|
||||
`state<Slot>` 값이 steady state에서 교체될 때는 부모 Blocker가 이미 꺼져 있어
|
||||
부모의 `gatedRecompute`가 아직 flush 안 끝난 `slot.Length`로 한 번 계산할 수
|
||||
있지만, flush가 끝나면 자기 교정된다 — 크래시도 영구 오류도 아닌 한 프레임짜리
|
||||
낭비라 **손대지 않기로** 했다.
|
||||
-> offset 을 먼저 설정해 주어, 계산된 오프셋을 받은 다음 리스트의 액티베이션 이후 Length 를 확정된걸 setLength 하고 나서 마운팅 처리를 하는데, 이 경우 length 가 여러번 계산되어 낭비가 나진 않는거로 알고있음. 단순 위치계산 루프가 한번 돌지만, offset 이 전부 같아 set 안 일어나고 가벼운거로 아는데, 아님?
|
||||
|
||||
### SL-59 — 재귀적 `Clear()` 금지
|
||||
죽는 서브트리 내부에서 요소 수만큼 shift+recompute가 반복되므로, 순수 파괴 walk만
|
||||
하고 outer 쪽 recompute는 자기 위치 하나에 대해 한 번만 돈다. → **예/아니오**
|
||||
-> 실 상황에 대한 설명 더 필요함. 이 글만 보아서는 어떤 상황인지 정확히 판단 어려워보임. 판단 보류.
|
||||
|
||||
### SL-63 — Length 변경은 offset 변경으로만 전파
|
||||
`recompute`가 `:Set()`하는 대상은 (a) 뒤 형제들의 offset, (b) owner가 Slot이면 그
|
||||
`.Length` 둘뿐이고, Length 값 자체는 읽히기만 한다 — 새 전파 채널이 아니다.
|
||||
-> (b) 가 뭔가 이상함. 오너의 length 를 직접 설정하지는 않을것임. 자신 length 를 변경하면, 자동으로 observer 에 등록된것으로 인해 length 가 업데이트 되는 방식일텐데, 그렇지 않음? 최종 리컴퓨팅 결과가 length 가 되는거 아니였음? 그걸 위해 blocker 로 전부 블록 하고도 레이아웃이 멀쩡하지만, 아래에서 offset/length 확인 순회 도는거 아녔음?
|
||||
|
||||
### SL-72 — `dispose` 범위에서 Observer/Effect 제외
|
||||
Observer/Effect는 생존이 gcconn만으로 판정되고 "죽는 순간 트리 부기가 어긋나는"
|
||||
문제가 원천적으로 없어 dispose 대상이 아니다 — 조기에 끊으려면
|
||||
`unbindLifetime`으로 충분하다.
|
||||
-> 우린 조기에 끊는걸 명시적으로 unbindLifetime 로 지원하지 않음. 그건 유저에게 드러나는 표면이 아니고, State<Observer?> 를 사용하는게 적절.
|
||||
|
||||
### SL-74 — ⚠️ 미해결 항목 확인: `SetAndDispose`
|
||||
`Get()` → `Set(new)` → 옛 값 `dispose`라는 3단계가 불편하다는 지적에서 나온
|
||||
`source:Apply(SetAndDispose(new))` 또는 `source:SetAndDispose(new)` 후보가 열려
|
||||
있고, 전자는 `Apply`가 `State`가 아니라 **`Source`를 넘겨주는 함수**여야 하므로
|
||||
`state:Apply` 시그니처에 영향이 갈 수 있어 **M3 착수 전 방향만이라도** 정해야
|
||||
한다.
|
||||
-> 타입 문제 때문에 Apply 를 오버라이딩 해서 source 타입을 함수에 건내주는건 못함. 그럼 source -> state 가 안전히 성립 못해서, Apply 라는 이름을 그대로 쓰지는 못함. 따라서 영향이 안 가고, 그냥 SetAndDispose() 로만 Set() 와 세트로 주는게 나아보이고, 그걸로 확정지어야할것 같다는 생각임.
|
||||
|
||||
### SL-75 — 해제 = 0/`None` 재등록
|
||||
별도 unregister API는 필요 없고 `setOffsetSource(None)` → `setLength(0)` 재등록이
|
||||
곧 해제다. 순서가 중요한 이유는 값이 틀려져서가 아니라 **죽는 중인 Source에
|
||||
쓰기가 날아가기 때문**이고, 해제 시 `slot.Offset = nil`도 같이 해야 stale한
|
||||
Offset을 공개하지 않는다.
|
||||
-> 아님. nil 로 만들면 안 되는게, 포탈로 옮기는게 안 됨. 이미 offset 을 들고 가 바운딩 했다면 큰 문제가 생김. 그냥 stale하게 있는게 맞고, 나중에 offset이 멀쩡히 다시 설정되는게 옳음. 언마운트 시 offset stale 은 단순히 맞는 행동이고, 처음 생성 시 0 인것과 유사 동작임.
|
||||
|
||||
### SL-76 — `recompute`는 `nil`도 관대하게 skip
|
||||
정상 상태에선 항상 `None`이 계약이지만 해제/재마운트 전이 구간에서 `nil`이
|
||||
관측돼도 크래시 대신 skip이어야 한다 — 계약 완화가 아니라 순수 방어이고 등록
|
||||
쪽은 여전히 `None`을 쓸 의무가 있다.
|
||||
-> 말을 더 정리해주길 바람. 애초에 해제에서 nil이 관측 될 일이 없다고 생각하는데, 그게 아니라면 다시 더 자세히 말을 해주길 바람. 이것만으로는 판단이 어려움
|
||||
|
||||
### SL-78 — nested-Slot 결과의 Length만큼 건너뛰기
|
||||
`updateFn`이 nested Slot을 반환하면 그 아이템은 물리적으로 `result.Length`개를
|
||||
차지하므로 `pos`가 그만큼 건너뛴다. 남는 캐비엇은 `index`가 raw 스냅샷이라 nested
|
||||
Slot의 Length가 outer reconcile 없이 나중에 바뀌면 이후 형제들의 `index`가 갱신
|
||||
안 된다는 것이고, 이건 "`index`는 raw number" 설계의 당연한 연장이라 실시간
|
||||
정확성이 필요하면 `updateFn`이 직접 처리해야 한다. → **예/아니오**
|
||||
-> 의미를 모르겠음. 애초에 slot 내의 index 와, length 로 구해진 offset 은 다른 개념인데, 너무 섞어 말하는것 아닌지 생각해보길 바람. 또, Length 업데이트는 상위 slot 이 observe 하기에 형제 slot 갱신에 무관한데, 그 이야기가 아닌것임? LayoutOrder 내에서 index 만 쓰는게 아니다를 말하고 싶은건지, 더 자세히 말해주길 바람.
|
||||
|
||||
### AT-11 — ⚠️ 열린 항목 확인: `Frame { a, a }`
|
||||
같은 그룹 객체를 두 위치에 놓으면 `groupKey(v, name)`이 그룹 객체별·이름별
|
||||
메모이즈라 **완전히 같은 키**가 나와 claim 체크를 통과하고, 두 위치가 하나의
|
||||
체인을 공유하다가 `k=1` retract가 `k=2`의 바인딩까지 철거한다. `Ref`처럼
|
||||
`bindLifetime`으로 막을 수 **없는** 이유는 그룹 Attribute 값은 여러 곳에서 쓸 수
|
||||
있어야 하기 때문이고, 그래서 **위치별 claim 레지스트리를 하나 더** 두기로 방향은
|
||||
확정됐으나 **키를 무엇으로 할지와 `nameClaims`와의 공존은 미정**이다.
|
||||
-> groupClaimKeys 정도로 확정. 더 나은 답이 있다면 적어주길 바람. 다만 충분하다고 생각하는 이름임.
|
||||
|
||||
### AT-13 — 해제→재클레임 순서는 Dispatch가 보장
|
||||
같은 핸들러 재프로세스는 `retractor(v)` → `process`, 핸들러가 바뀌면
|
||||
`retractFrom` → `process`라 어느 경로든 옛 claim 반납이 먼저다.
|
||||
-> 정확히는 같은 핸들러 재프로세스는 retractor 를 process 에서 굴리고 자기 작업을 함. 따라서 process → calls retractor(v) → process new one 이 맞는걸로 보이는데. 단순 축약상 정보유실인것 뿐이지, 실제 구현은 저렇게 알고 있는게 맞음?
|
||||
|
||||
### AT-20 — 생존 이름도 매 사이클 철거→재등록
|
||||
클로저는 인자(새 값)를 안 보고 자기가 등록한 키 전부를 균일하게 철거하며, 비용은
|
||||
`StoreBind` 재구독과 같은 값 `setAttribute` 한 번뿐이다. 그룹 전용 체인이 된
|
||||
지금은 최적화도 가능하지만 옛 이름 집합을 또 들고 있어야 해 부품이 늘어나므로
|
||||
**기본은 균일 철거 유지**다.
|
||||
-> 생존 이름에 대해서 최적화 불가함. 실 value 자체가 바뀌고 같은 값인지 비교를 해야하는데, 그러면 값을 진짜 까봐야 하고, 이전 값을 알아야하기 때문. 이름 목록의 변경으로 최적화가 되는 요소가 아님.
|
||||
|
||||
### TW-12 — `CanAnimate`
|
||||
생략하면 기본 `true`이고, `false`로 resolve되면 `Tween`으로 안 감싸고
|
||||
`self:Get()`을 그대로 반환한다 — reduceMotion류 접근성 우회가 이 필드 하나로
|
||||
표현된다. 케이싱은 나머지 필드와 맞춰 `CanAnimate`(PascalCase)다.
|
||||
-> 단순 boolean 으로만 설명했는데, 정확히는 CanAnimate: state<boolean> | boolean | nil 이다. 이미 그럴것으로 보이고, 아니라면 정정해야함. 다른것과 똑같게, 필요 시 바로 Get()
|
||||
|
||||
### TW-16 — `initValue`는 에이전트 범위 밖
|
||||
초기 진입 애니메이션은 필요해지면 **사용자가 직접 코드베이스+문서를 만지기로**
|
||||
확정됐고, 에이전트는 임의로 착수하지 않는다.
|
||||
-> 틀리진 않았는데, Human todo 에 언급이 없음.
|
||||
|
||||
### UI-5 — ⚠️ 확인 필요 항목
|
||||
숏핸드가 만드는 **자식**도 quad가 만든 Instance이므로 gcconn/gchold 셋업을
|
||||
거치는지 구현 시 확인해야 하고, 안 거치면 여기서만 조용히 미아가 된다.
|
||||
-> 애초에 똑같이 process 로 위임하는 이상, gcconn/gchold 없으면 옵저버 바인딩 부터 실패함. 일반 요소처럼 똑같이 UI...{} 처럼 생성되어도 되고, 어떤 방식으로든 gcconn/gchold 가 셋업되는게 맞음. 확인했고 해소된 요소가 될듯.
|
||||
|
||||
### UI-8 — `mapTweenValue`가 필요한 이유
|
||||
`v`가 `Tween<number>`면 `wrap` 변환을 **`Tween`을 벗기지 않고 `.Value`에만**
|
||||
적용해야 하므로, `table.clone` 후 `Value`만 교체해 `Tween(opts)`로 다시 만든다.
|
||||
`wrap`이 항등인 키도 분기 없이 이 헬퍼를 거친다.
|
||||
-> 그냥 펑터 구조를 그대로 줘도 무방한듯. :Map 정도로써 새 Tween 을 새 관측된 Value 로 형성. Tween<T>:Map(T) -> Tween<T> 가 타입 상 안전히 가능하다. 이는 Tween<T> 에 대한 기본 정의를 두고 다른곳에서 Map 해서 재사용하는 구현도 가능케 할수도 있게 보이긴 하나, 사용 케이스가 넓지는 않을것. 단 외부에 보이는게 무해하고, 어차피 내부 구현 상 필요하므로 같이 만들어도 좋아보임. 혹은 Mapped 의 immutable 의 ed 형태를 써도 좋아보임.
|
||||
|
||||
### UI-11 — 자식 파괴 시 `retractFrom` 호출
|
||||
자식을 파괴할 때 실행 중인 엔진 Tween이 남아있을 수 있으므로
|
||||
`Dispatch.retractFrom(child, prop, 1)`을 같이 부르는 게 정석이고, retractor 안에서
|
||||
**다른 키**에 대한 `retractFrom`은 허용된 경로다. → **예/아니오**
|
||||
-> 자식 파괴 시 사실 Tween 은 엔진에 의해 자동 멈춤/무효/삭제 처리되고, 트윈 자체가 retract 되어도 아무것도 안 하는 nop 라 의미가 없을것이다.
|
||||
|
||||
### ML-5 — 멱등 가드는 `module`을 키로 하는 `Relate`
|
||||
각 `InitXxx`가 파일 스코프에 `Relate()` 하나를 두고 `module`을 키로 "이 인스턴스에
|
||||
이미 Init됐는지"를 기록한다. `require` 캐시로는 부족한 이유는 그게 **파일**
|
||||
단위인데 `New()`는 여러 `module` 테이블을 만들 수 있어서다. → **예/아니오**
|
||||
-> 새로운 커밋에서 이것이 달라짐.
|
||||
|
||||
### ML-9
|
||||
`Quad.debug: boolean`(기본 `false`)은 라이브러리 자체의 디버그 스위치이고 지금
|
||||
게이팅하는 건 핸들러 우선순위 동률 경고 print다. 기본이 `false`인 이유는
|
||||
라이브러리가 사용자 콘솔에 아무것도 안 찍는 게 기본이어야 하기 때문이고,
|
||||
다중 인스턴스화 시 인스턴스별인지 전역인지와 `listHandlers()`가 이 표면에
|
||||
속하는지는 **미정**이다.
|
||||
-> D-8 가 해소시킴
|
||||
|
||||
### LH-8 — 스코프 판단이 틀렸던 것
|
||||
"(a) 자기 프로퍼티만 / (b) 서브트리 전체" 중 "(a) 메커니즘은 (b)를 못 준다"는
|
||||
판단이 틀렸음이 드러났다 — 배열 파트 루프가 각 자식의 마운트를 동기적으로
|
||||
끝내므로 (a) 메커니즘이 사실상 (b) 스코프를 공짜로 준다. 진짜 경계는
|
||||
(a)/(b)가 아니라 **"자기 아래 vs 자기 위"**였다.
|
||||
-> 사람이 너무 이해하기 어려운 표현임. 틀린말은 아닌듯 한데, 너무 어려워서 이것만 보고 판단은 안될듯. 풀어 보여줘야함.
|
||||
|
||||
+
|
||||
`OnRendered`라는 이름이 `componentDidMount` 같은거 구현 가능하면 좋긴하겠는데 별로 애매한가 생각중... 화면 그려지기 전에 애니메이션이 된다던가 하지 않게 하는 방안이 있음 좋아보임. 하지만 나중에 얹어져도 좋을 이야기이고, 당장은 사용사례가 안 보이므로 추가 프리미티브에 백로깅만 하고, 나중에 필요하다는 의견이 나오면 재생각 해볼 예정.
|
||||
|
||||
+
|
||||
destroySlotTree 가 소유를 명시적으로 지워야할 이유가 있냐 의문. Destroy 된 요소는 다른곳에 원래 마운트 못하는게 보통 엔진 정상이고, 또, 릴리즈 안 되어 다른곳에 마운트 막혀도 상관 없고, 그게 정상 동작일 수 있어보임. 정확한 형태가 어떤지 알아봐야할 상황.
|
||||
|
||||
+
|
||||
Length 를 통해 '먼저 밀어내고 나서' 그 공간에 넣는다가 지금 관행인데, 다른 crud 와 list 도 이게 통하는지 봐야함. 만약 밀어내고 당기지 않은 상태에서 그 공간에 넣는다 하면, 밀어내는걸 구현해야하는 백엔드에서 골치아파짐. 지금 어떤 상황인지 확인해볼것
|
||||
|
||||
+
|
||||
지금 명시적으로 Attribute(store1, store2, ..., {plain = "table도 됨"}) 로만 되어있고, plain= 에 state/source/T 가 올 수 있음을 안 알려주는 모양으로 나오는데, 그렇게 적어도 된다 생각함. 엔지니어링 비용이 없고, 그냥 이미 그렇게 구현되도록 만들 계획이였다고 생각함. 그리고 뒤에 오는기 이기는 것 또한, 잘 명시되어있나 봐야함.
|
||||
3035
.claude/qa-request/pre-implementation-qa-round4.md
Normal file
3035
.claude/qa-request/pre-implementation-qa-round4.md
Normal file
File diff suppressed because it is too large
Load diff
71
.claude/session/2026-08-19-04-qa-round4-questionnaire.md
Normal file
71
.claude/session/2026-08-19-04-qa-round4-questionnaire.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# 2026-08-19-04 — 구현 전 QA 4라운드 문항지 작성 (회신 대기)
|
||||
|
||||
**한 줄**: 사용자 요청으로 `base/` 확정 전체를 "예가 나와야 정상인 문항"으로
|
||||
전수 문항화해 `qa-request/pre-implementation-qa-round4.md`를 만들었다.
|
||||
**설계 결정은 하나도 안 내렸고, 정정도 하나도 안 했다** — 회신 대기 상태.
|
||||
|
||||
## 사용자 요청 원문
|
||||
|
||||
> 요즘 변경이 엄청 많고, 틀려서 정정한게 엄청 많아서, 모든 부분에 있어서 내
|
||||
> 심사를 좀 받아야할듯. 예 가 되어야하는 질문들을 계속, 모든 확정 부분에
|
||||
> 있어서 해줘. 표면적 타입계약부터, 실제 내부 구현 계획과 동작 원리 등, 모든
|
||||
> 부분에 있어서 내가 검토해줄게. 우선 틀린게 있으면, 기록만 해둬. 정정은
|
||||
> 나중에 틀린 부분을 모아서 한번에 내가 어디가, 어떻게 왜 틀렸는지, 원래 뭐가
|
||||
> 맞는지 기술해서 Markdown 서식으로 보고해줄게. 따라서, 아니요가 된 부분만
|
||||
> 모아서 하나의 문서로 만들어줘. 일종의 pre implementation QA 라고 봐도 될듯.
|
||||
> 파일은 원하는 위치에 둬. 이전 라운드 처럼, qa-request 안에 정리해도 될거야.
|
||||
> 서브에이전트는 쓰지 말아줘. 한 맥락에서 자연스럽게 흐르는 전체 구조를 보는게
|
||||
> 필요해보임.
|
||||
|
||||
## 작업 방식
|
||||
|
||||
- **서브에이전트 금지 지시를 그대로 따름** — `.claude/base/` 26개 문서
|
||||
(약 14,900줄)를 메인 컨텍스트에서 의존성 순서로 직접 읽었다. `conventions.md`의
|
||||
"소스코드를 많이 읽어야 하는 리서치는 Agent로 위임" 관례에 대한 **이번
|
||||
요청 한정 명시적 예외**(사용자가 "한 맥락에서 자연스럽게 흐르는 전체 구조"를
|
||||
이유로 지정).
|
||||
- 읽는 순서는 1라운드가 쓴 의존성 순서를 재사용: architecture → source-state →
|
||||
store → bind-system → dispatch-core → lifecycle-pattern → relate → brand →
|
||||
modifier → blocker/effect/event/onchange/purity → ref → slot → tag →
|
||||
attribute → tween → ui-shorthand → component-composition → module-lifecycle →
|
||||
lifecycle-hooks → fallback → typing-limits → debounce-throttle.
|
||||
- **컨텍스트 유실 대비로 섹션별 증분 기록** — 문서 몇 개를 읽을 때마다 그
|
||||
구간의 문항을 파일에 append했다(전부 읽고 나서 한 번에 쓰면 중간에 컨텍스트가
|
||||
잘릴 때 통째로 날아감).
|
||||
|
||||
## 산출물
|
||||
|
||||
`.claude/qa-request/pre-implementation-qa-round4.md` — 문서 코드 26개
|
||||
(`A`/`S`/`ST`/`BS`/`D`/`LP`/`RE`/`BR`/`M`/`SL`/`BK`/`E`/`EV`/`OC`/`R`/`T`/`AT`/
|
||||
`TW`/`CC`/`UI`/`ML`/`DT`/`LH`/`FB`/`PE`/`TL`)로 나눈 전수 문항지. 문항 수와
|
||||
문서별 분포는 그 파일 자신이 소스(여기서 반복 안 함 — `conventions.md`의
|
||||
"개수·목록·상태는 소스를 하나만 둘 것").
|
||||
|
||||
**문항을 쓸 때 지킨 것**:
|
||||
- 각 문항은 `예/아니오`로 답할 수 있는 **단정문** — "무엇을 확정했나"가 아니라
|
||||
"이렇게 확정한 게 맞나"를 묻는 모양.
|
||||
- **근거까지 같이 넣음.** 결론만 물으면 사용자가 "왜 그렇게 정했더라"를 다시
|
||||
찾아야 하고, 근거가 틀렸는데 결론만 맞는 경우를 못 잡는다. 실제로 코퍼스엔
|
||||
"결론은 그대로인데 근거가 뒤집힌" 사례가 여럿 있다(`Tag`의 holders 키잉,
|
||||
`Blocker` 게이팅의 필요 이유가 크래시→비용으로 바뀐 것 등).
|
||||
- **⚠️로 열려 있다고 적힌 항목은 "확정이 맞나"가 아니라 "아직 열려 있다는 인식이
|
||||
맞나"를 묻는 문항으로 별도 표기**(`S-38`/`ST-3`/`ST-8`/`E-10`/`SL-45`/`SL-74`/
|
||||
`AT-3`/`AT-11`/`UI-5`/`ML-9`).
|
||||
- 문항 자체가 문서를 오독했을 가능성도 회신 대상으로 명시(회신 안내 절).
|
||||
|
||||
## 이번 세션이 **안** 한 것 (중요)
|
||||
|
||||
- **정정 0건** — 사용자 지시("우선 틀린게 있으면 기록만 해둬")대로 `base/`를
|
||||
하나도 안 고쳤다. 읽는 도중 "이건 좀 이상한데" 싶은 게 있어도 문항으로만
|
||||
만들었고 임의 판단을 넣지 않았다.
|
||||
- **감사 루프 안 돌림** — `quad-doc-auditor`는 "중대 변경 후"에 도는 것인데
|
||||
이번 변경은 신규 파일 1개 + 인덱스 2줄이라 대상이 아니다. `doc-check.py`는
|
||||
돌려 ERROR 0 확인(신규 파일이 처음 낸 절 참조 불일치 1건은 그 자리에서 고침).
|
||||
|
||||
## 인덱스 레이어 반영
|
||||
|
||||
- `.claude/README.md`의 `qa-request/` 행에 4라운드 항목 추가(**회신 대기** 상태
|
||||
명시 — 1~3라운드는 "반영 완료"라 성격이 다름).
|
||||
- `.claude/todos.md` 00번을 "1·2·3라운드 완료 + 4라운드 회신 대기"로 갱신.
|
||||
- `question.md`는 **안 건드림** — 아직 사용자 판단이 나온 게 없어서 추가할
|
||||
항목이 없다.
|
||||
|
|
@ -5,8 +5,18 @@
|
|||
(`.claude/question.md`, `luau-test/STATUS.md` 등).
|
||||
|
||||
|
||||
00. **⭐⭐ [2026-08-18 신설, 같은 날 완료] 구현 전 QA — 1·2·3라운드
|
||||
전부 `base/`에 반영 완료.** 1라운드는 사용자가 `base/` 확정 문서 전체를
|
||||
00. **⭐⭐ [2026-08-18 신설] 구현 전 QA — 1·2·3라운드는 전부 `base/`에 반영
|
||||
완료, [2026-08-19 신설] 4라운드는 문항지 작성만 끝나고 사용자 회신 대기 중.**
|
||||
|
||||
**4라운드(`.claude/qa-request/pre-implementation-qa-round4.md`) — 회신
|
||||
대기.** 사용자 요청("모든 확정 부분에 있어서 예가 되어야하는 질문들을
|
||||
계속 … 표면적 타입계약부터, 실제 내부 구현 계획과 동작 원리 등")으로
|
||||
`base/` 전 문서를 한 맥락에서 읽으며 확정 주장을 전수 문항화한 것.
|
||||
**아직 아무것도 정정하지 않았음** — 사용자가 "아니오"인 항목을 회신하면
|
||||
그때 `base/`에 반영하고 그 문서를 1라운드처럼 근거 기록으로 재편한다.
|
||||
문항 수/분포/읽는 순서는 그 문서 자신이 소스(여기서 세지 않음).
|
||||
|
||||
**1~3라운드(완료)**: 1라운드는 사용자가 `base/` 확정 문서 전체를
|
||||
문항으로 재심사한 결과(원본 문답과 사용자 답변 원문은
|
||||
`.claude/qa-request/pre-implementation-qa-round1.md`가 소스), 확정으로
|
||||
적혀 있는데 실제로는 틀린 항목이 여러 건 나왔고 **같은 날 전부 정정
|
||||
|
|
|
|||
Loading…
Reference in a new issue