quad/.claude/project-context.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

9.4 KiB

프로젝트 컨텍스트

루트 CLAUDE.md@import 하는 파일. 폴더별 상세 색인은 .claude/README.md가 소스 — 여기선 중복 서술하지 말고 가리키기만 할 것.

이 프로젝트가 뭔지

Roblox 엔진에서 동작하는 DOMless UI 렌더러 quad를 처음부터 다시 짜는 프로젝트. 목표는 개별 프로덕트가 아니라 라이브러리로서의 코드 퀄리티와 지속 가능성 — 빠른 이터레이션보다 정확성/설계 정합성이 우선. 작업 기간은 길게 잡음.

[2026-08-24 기준] M0(스파이크 검증)/M1(스캐폴딩) 완료, 다음은 M2(반응형 코어 — Source/State/Store)(마일스톤이 넘어갈 때 루트 CLAUDE.md 머리말도 같이 고칠 것 — 같은 상태를 두 곳이 서술하고 있음). ⚠️ [2026-08-24] M2와 M3의 번호·순서가 맞바뀌었다 — 열려 있던 마일스톤 순서 문제가 (a) 순서 교체로 닫힌 결과다(경위는 archive/question-resolved.md의 "마일스톤 경계" 절, 새 구성은 ROADMAP.md의 M2 배너). 2026-08-24 이전에 쓰인 session/·archive/·qa-request/M2/M3는 옛 의미(M2=디스패치, M3=반응형)다. 그 교체의 부작용으로 한때 question.md 최우선 절에 항목 둘이 올라와 있었으나, [2026-08-25] 둘 다 닫혀 그 절은 지금 비어 있다 (중간 State GC는 _hold 불변식으로, store:GetDynamic 위치는 콜론 유지 + CheckReserved 타입 함수로 — 7라운드 손 트레이싱 후속, 결정 전량의 소스는 qa-request/pre-implementation-handtrace-round7-followup.md). M2 착수를 막는 항목은 설계든 실측이든 없다. 저장소 루트에 quad-base/src/(New()/RunInit/AddPlugin/Relate/Debug)/ quad-types/src//type-version-check/src/가 실제로 존재(quad-roblox/src는 아직 빈 폴더 — M5에서 채워짐), 자세한 진행 상황은 루트 ROADMAP.md가 소스. ⚠️ [2026-08-25] 테스트는 ./scripts/test.sh로 돌릴 것luau CLI가 심볼릭 링크를 못 타는데(디렉토리·파일 둘 다) pesde의 워크스페이스 링크가 전부 심볼릭이라, 그냥 luau로 돌리면 스모크 2개가 죽고 luau-analyze조용히 통과한다(모듈을 any로 떨어뜨림 — "거짓 클린"). 그 스크립트가 scripts/relink.sh를 먼저 돌려 심볼릭을 실제 복사로 바꾼다. 핵심 아키텍처(Store 책임 분리, process/retract 디스패치 모델, Store/State/Source 온톨로지, 소스 트리 구조, Modifier 메커니즘, 컴포넌트=플레인 함수, 컴포넌트 경계 modifier/Ref 전달)는 전부 .claude/base/에 문서로 확정돼 있음 — 먼저 .claude/base/architecture.md를 읽을 것. 사용자가 직접 "지금 quad에서 가장 문제되는 부분"으로 지목했던 컴포넌트화(특히 modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션에서 수렴 완료(base/component-composition-plan.md) — 남은 핵심 설계 질문은 없고, 용어 정리(진행 중)와 실제 스캐폴딩만 남음, .claude/todos.md 참고.

이전에 시도했다 폐기한 v2 재작성 시도(.claude/initreq/quad2-try)도 리서치 완료 — OOP 상속/커스텀 파서/Slot 스텁/Pipe copy-on-write 절충안은 확인된 죽은 접근이라 반복 조사 금지(base/bind-system-plan.md "확정된 것" 절 참고).

계획 문서 구조

.claude/README.md가 색인. 요약:

  • [2026-08-16] 루트 CLAUDE.md는 짧은 진입점일 뿐이고, 실제 내용은 .claude/conventions.md(관례·작업 방식) / 이 문서 / .claude/todos.md (지금 할 일)로 쪼개져 @import로 다시 합쳐짐. .claude/session-summary.md (세션 요약 색인)만 import 안 됨 — 필요할 때 직접 열 것.
  • .claude/base/ — 확정된 아키텍처/컨텍스트, plan/done 개념 없음. 먼저 .claude/base/architecture.md를 읽을 것.
  • .claude/reference/[2026-08-07 신설] base처럼 확정된 건 아니지만 base 문서가 근거로 인용하는 온디맨드 참고 자료(v1 내부 동작 스냅샷, Fusion/Vide 비교 리서치) — 항상 읽을 필요는 없고 인용될 때만 열어볼 것. [2026-08-21 확장] 확정된 결정의 근거 기록(그 결정이 왜 그렇게 났는지)도 여기 둠 — research/를 떠났지만 archive/ 대상은 아닌 것들. 어떤 문서가 있는지는 .claude/README.md가 소스.
  • .claude/research/ — 아직 착수 전, 사용자와 상의 필요한 설계 논의. 전부 후순위. 어떤 문서가 있는지·우선순위가 뭔지는 여기서 세지도 나열하지도 않고 .claude/README.mdresearch/ 표로 미룸(개수뿐 아니라 파일명 나열 자체가 새 문서가 추가될 때마다 stale해지는 패턴이 실제로 반복됐음 — 과거엔 "두 개뿐"이라 적어놨다가, 2026-08-16엔 7개짜리 나열이 실제 11개와 어긋난 걸 감사가 발견. 아래 luau-test/audit 문단과 같은 처리로 통일). research/를 떠난 것만 짚으면: tween-plan.md는 2026-08-12 세션에 마지막 열린 항목까지 전부 해소돼 base/로 승격, 이미 생성된 인스턴스 재바인드는 2026-08-14 세션에 기각돼 archive/existing-instance-bind-rejected.md로 이전 — 둘 다 더 이상 여기 없음.
  • .claude/luau-test/[2026-08-09 신설] "추론만으로 확정하고 실제 Luau로 부딪혀본 적 없는 것"을 미리 검증하는 독립 실행 스파이크 모음 (luau <파일> / luau-analyze <파일>). 상태의 소스는 항상 STATUS.md (pass / 사람 결정 필요 / 스파이크 깨짐 / 미실행, 폴더 구조 자체가 상태), 각 파일이 뭘 왜 검증하는지는 README.md. 2026-08-13에 첫 실측 완료(당시 런타임 12개 전원 통과) — 이후 여러 세션에 걸쳐 재설계로 몇 건이 추가로 rewrite-required/에 합류했으니 총 몇 개인지도, 지금 몇 개가 어디 있는지도 여기서 세지 않고 STATUS.md로 미룸(세거나 나열하다 stale해지는 패턴이 실제로 반복됐음, .claude/todos.md의 luau-test 스파이크 항목 참고).
  • .claude/audit/[2026-08-13 신설] 스파이크를 실제로 돌린 실측 결과 기록(계획 아님). 부분 확인도 있는 그대로 남김 — 지금 몇 개가 있는지·각각 뭘 확인했는지는 여기서 나열 안 하고 .claude/README.mdaudit/ 행으로 미룸(luau-test와 같은 이유 — 나열하다 새 폴더가 추가될 때마다 stale해지는 패턴이 실제로 반복됐음, 가장 최근엔 2026-08-15에 이 목록이 3개에서 멈춰 있는 걸 /code-review가 발견). type-recursion-issue/만 참고로 짚으면: [13차 세션] 0-Y 재실측 전체 — REPORT.md + spikes/(개수는 폴더가 소스), 스크립트를 같이 두는 예외적 구성(판정이 "여러 formulation 대조"라 개별 파일을 직접 돌려야 재현됨), 결론은 base/typing-limits.md로 승격 — 이후 신설된 폴더들도 같은 구성 관례를 따름(type-recursive-issue-with-typeof/, type-recursive-issue-try-callback/ 등).
  • .claude/qa-request/ — 원래는 "구현이 끝나고 사용자 실기기 QA만 남은 것"을 담는 폴더였으나, [2026-08-18] 구현 전 사용자 심사 라운드의 산출물도 여기 둠(pre-implementation-qa-round1.md/pre-implementation-qa-round2.md/ pre-implementation-qa-round3.md 전부 완료 — 라운드마다 새 파일, 상태의 소스는 각 파일 자신). .claude/feedback/실사용 피드백용(M0/M1 스캐폴딩이 아니라 실제로 렌더링해보고 쓰는 단계부터), [2026-08-19 기준] 폴더 자체가 아직 없음 — 첫 피드백이 생길 때 만들면 됨. .claude/archive/는 원래 같은 취급이었으나 2026-08-06 세 번째 세션부터 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용 시작(구현 완료 대상만이 아님) — archive/store-source-proxy-reversed.md가 첫 사례, 나중 quadnomicon 콘텐츠 소재로 재사용 예정.
  • .claude/session/[2026-08-11 신설] 세션별 상세 로그 원문(시행착오· 정정 전 서술 포함, quadnomicon 개발로그 소재용) — 루트 CLAUDE.md가 3000줄 넘게 불어나 성능 저하를 유발해서 분리함. .claude/session-summary.md의 각 항목이 여기로 링크. 항상 읽을 필요 없음 — 특정 결정의 논의 과정/시행착오가 궁금할 때만 열어볼 것, 지금 유효한 설계는 항상 base/가 소스.
  • .claude/initreq/ — 클론해둔 참고 레포(quad v1, Fusion, Vide, rbvm, tbox, code-docker) + PA님 실 코드(artworks/) + 원본 요청. 읽기 전용, .gitignore로 커밋 제외됨 — 내용을 다른 곳으로 옮기지 말고 항상 원본 그대로 둘 것. 리서치가 더 필요하면 이 폴더를 다시 파고들 것.
  • .claude/question.md — 사용자가 답해야 할 질문 전체 취합(우선순위순).
  • 루트 ROADMAP.md — 설계 단계 종료 후 실제 구현 순서(M0, M1, ... 마일스톤 + todo 체크박스). "무엇을 확정했는가"는 .claude/base/가 소스, "어떤 순서로 만드는가"는 이 문서가 소스 — 헷갈리지 말 것.
  • 루트 HUMAN_TODO.md — 사람만 할 수 있는 일(로컬 GUI 조작, 스케줄/루프 설정 등).