quad-roblox가 quad-base를 런타임 주입(QuadRoblox(Quad): QuadRoblox)으로만
받으면 pesde 의존 선언이 필요 없어 보이지만, 타입 참조용 require도
런타임에 실제 실행됨을 실측 확인 — dev-dependency로 두면 게시 후
소비자 환경에서 크래시함. 해법으로 구현 없는 타입 계약 전용 워크스페이스
패키지 quad-types 신설, quad-base/quad-roblox 모두 이것만 의존하도록
전환.
AddPlugin<Self,P>(self:Self,fn:(Self)->P):Self&P — 제네릭 self로 둬야
체이닝이 누적됨을 실측 확인(고정하면 이전 확장을 잃음), quad-base에
실제 mutate 기반 구현 반영.
CheckedQuad<T> 버전 체크는 배선하며 세 번 깨짐 — error() 대신
print+types.never, 함수 본문 로컬 별칭 대신 리턴 타입 표현식에 직접,
그리고 가장 중요하게 type function을 한 번이라도 거친 값(패스스루
포함)은 이후 AddPlugin 같은 제네릭 self 체이닝이 조용히 깨진다는 새
Luau 함정 발견 — typing-limits.md §6으로 승격. 최종 설계(검증 결과를
원본과 격리된 가상 필드로)만 AddPlugin과 완전히 호환.
부수로 quad-base 자신도 quad-types workspace 의존 때문에 CLI symlink
함정(지난 세션 발견)에 걸림 — 로컬 테스트용 symlink 실체화로 임시 우회.
Co-authored-by: qwreey <me@qwreey.moe>
Word30210/roblox-project-example(initreq/에 클론)를 참고해 두 가지 채택:
(1) rokit.toml → mise.toml — mise install이 pesde/rojo/luau-lsp/selene을
GitHub attestation+SLSA provenance 검증까지 거쳐 설치하는 걸 이 샌드박스
에서 직접 확인(rokit은 끝내 검증 불가), 이 환경 자체가 이미 mise로
luau를 관리 중이라 더 자연스러움. (2) selene 린터 — 참고 레포의
selene.toml을 패키지별로 채택, 단 CWD 상대 config 탐색 함정을 발견
(루트 단일 설정으로 두면 다른 디렉토리에서 실행 시 조용히 Lua 5.1
std로 폴백해 Luau 타입 문법 전체가 파싱 에러로 잘못 보임) — 참고 레포
그대로 패키지별 selene.toml + 패키지 안에서 실행하는 걸로 확정. 도입
즉시 smoke.mock.luau의 assert 메시지 누락 3건을 잡아 수정.
darklua의 convert_require 변환은 검토 후 기각 — 사용자 판단: Roblox
엔진 자체도 이미 같은 require-by-string 의미론(@self/@game)을 지원해
변환 계층이 불필요.
Co-authored-by: qwreey <me@qwreey.moe>
InitRoblox(Module) backend 주입 패턴을 quad-base 자기 내부(Dispatch 등)에도
대칭 적용 — 각 서브시스템이 InitXxx(module)로 module을 뮤테이션. 서브시스템
간 호출 순서 문제는 각 InitXxx 파일 톱레벨에 Relate() 하나를 두고 module을
weak key 삼아 인스턴스별 완료 여부를 기록해 require처럼 멱등하게 만들어
해소(relate-plan.md 체크리스트에도 용례 추가). module-lifecycle-plan.md에
"New()의 내부 구성" 절 신설, architecture.md/dispatch-core-plan.md/
ROADMAP.md(M1 체크리스트)에서 상호 참조.
핸드오버 감사 루프 4라운드(무발견 1회로 수렴) — 라운드 1~2는 절 인용
사각지대·상호참조 누락·SetWeak/SetStrong 일관성을 잡았고, 라운드 3~4는 그
수정 자체가 남긴 커밋 개수/날짜 오기, 원문 인용 파라프레이즈 등을 추가로
잡음. 부수로 session/ 기록 공백(2026-08-18/19 다수 커밋에 원문 누락)을
발견해 이번 세션분만 session/2026-08-19-01-*.md로 남김 — 과거 공백 처리는
사용자 확인 대기.
Co-authored-by: qwreey <me@qwreey.moe>
직전 커밋에서 New()를 전부 Quad()로 치환했던 게 사용자 의도를 잘못 읽은
것이었음 — 사용자가 직접 바로잡음. 실제 설계: Quad(require의 반환값)는
이미 만들어진 기본 싱글톤 인스턴스이고, 그 안의 New 필드를 명시적으로
호출해야만 별도의 새 Quad 네임스페이스가 생긴다. "그냥 Quad()를 부르면
매번 새 인스턴스"였다면, 컴포넌트를 여러 모듈로 쪼갠 앱에서 각자
인스턴스를 "얻는" 것 자체가 "새로 만들기"가 되어 서로 다른 인스턴스가
생기는 사고로 이어졌을 것.
architecture.md/dispatch-core-plan.md/bind-system-plan.md/
module-lifecycle-plan.md의 New() 서술을 되돌리고, qa-request round1의
A-3 절에 후속 정정 문단을 추가했다. quad-doc-auditor로 두 라운드 감사해
새 발견 0건까지 수렴시켰다.
Co-authored-by: qwreey <me@qwreey.moe>
`:List` reconcile과 dispatch-core-plan.md의 recompute를 실제로 손으로
실행해보다 RC-1(배열 위치가 순차 등록되는 동안 아직 안 채워진 자리를
읽어 산술 에러가 나는 크래시, 정적 자식 2개짜리 Frame도 재현)을 찾았다.
같은 세션 후속 대화에서 사용자가 제시한 Blocker 재사용 게이팅 설계로
해결 — owner별 전용 Blocker가 배치 등록 동안 recompute를 막고,
setOffsetSource는 등록 즉시 앞선 형제 합을 직접 계산하며, attachSlot의
호출 순서(setOffsetSource→실체화→setLength→물리 마운트)도 바로잡고
코루틴 yield 금지 불변식을 명문화했다. Blocker에는 IsOn()/
OffWithoutEmit()이 새로 생겼다.
/code-review high가 이 diff에서 10건을 더 찾아 반영 — D-7 재역전과의
정합성, filter/nil 재역전 반영 누락, PreRef 가드의 typeof(k) 누락,
New()→Quad() 리네임 전파 누락, M6/M8 마일스톤 오기 등. quad-doc-auditor
감사 루프도 여러 라운드 돌려 새 발견 0건까지 수렴시켰다.
Co-authored-by: qwreey <me@qwreey.moe>
`.claude/pre-implementation-qa.md`(사용자가 base/ 확정 문서를 문항으로
재심사한 결과)를 실제 문서에 반영하고, 그 문서를 qa-request/로 옮기며
1라운드임을 파일명·제목에 명시(2라운드는 새 파일).
그대로 구현하면 반대로 돌던 것 2건:
- canBound의 판정 방향이 이름과 반대였음 → canBound(v) == not
isBoundAlive(v), 게이트는 전부 `if not canBound(v) then error(...)`.
canExecute와는 값이 같은 게 아니라 서로의 부정이고, 그게 오히려 이름
분리의 명분이 됨(옛 근거 "값이 항상 같다"는 폐기).
- gcconn/gchold 보관이 SetStrong으로 적혀 있었음 → SetWeak. 근거 문장까지
틀렸던 것이라 같이 교체(그대로 짰으면 두-Relate 상호 강참조 누수).
설계가 바뀐 것:
- Dispatch.drive의 None 스킵 분기 폐기 → NoneHandler는 재귀 전담,
NilHandler 신설(k=number and v==nil 말단이 setLength/setOffsetSource
등록). 깨진 전제는 "배열 파트의 None은 process를 안 탄다".
- Length/Offset 등록 책임이 "처음 매치한 Handler" → 말단 Handler.
- 이벤트 disconnect 센티널 false → None/nil.
- Ref 내부 구조를 .Callbacks 분리 + 평범한 .Value 필드로 단순화,
RefLeafHandler에 빠져 있던 type(k)=="number" 추가(leaf는 배열 전용).
- :List reconcile의 nil 리턴은 다시 파괴가 기본, 값 교체와 PopOnly(가칭)만
비파괴.
- base 소유 Fallback Handler 등록 주체를 백엔드 팩토리 → quad-base 자신으로
재역전(백엔드 미로드 시 안내 에러 경로가 안 돌았음).
- "이벤트 콜백 시그니처는 Luau가 검증 못 한다"가 거짓임이 사용자 반례로
확인 → onchange-plan.md의 파생 근거까지 교체(결론은 유지).
이름/표면: DI → D(Declarative) 확정 및 전수 반영, New 커링 + D는 전량
코드 생성, Attribute.Merged/Overridden 둘 다 제공, Quad.debug 신설,
store "key" 문자열 커링 기각(→ store:GetDynamic).
판단이 갈리던 4건(PopOnly 채택 / D-7 재역전 / NoneHandler·NilHandler 역할
분담 / 동적 키 경로)은 사용자에게 물어 확정.
커밋 전 검증: quad-doc-auditor 1패스가 1건, 사용자가 돌린
`/code-review high`가 10건을 더 잡아 전부 반영(ROADMAP이 SL-3 역전을 안
따라오던 것, 설계 갭 2건은 새 열린 질문으로 등록). doc-check.py ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
사용자 결정 — const 바인딩 재검토는 에이전트가 정할 문제가 아님. 논거:
pesde의 타입 추출(d.ts처럼 types emit)류 툴링이 미성숙해 const를 제공 못
하고 언제 가능해지는지도 불명확하며, 이는 사용자가 확인된 정보로 제공하는
게 맞다는 것. question.md에서 닫고 archive로 옮긴 뒤 HUMAN_TODO.md 8번
신설 — 사용자가 시점을 파악하거나 가능해질 때 에이전트에 알림. 그때까지
에이전트는 "일단 local로" 원칙 유지. architecture.md에 날짜와 논거 반영.
감사 4라운드(설계 코퍼스 각도):
- todos.md의 개수 하드코딩 2건 제거("체크리스트 8개", "11개 전원 완료")
— 이 프로젝트 자신의 "개수는 소스 하나만" 규칙 위반이던 기존 항목
- 코퍼스 확실 발견 0건(설계 문서 쪽은 깨끗)
주입 경로 실측 둘:
- 에이전트 정의는 이번엔 HEAD보다 1커밋 전 — 뒤처지는 폭이 실행마다 다름을
관측표에 4번째 행으로 추가
- CLAUDE.md @import 컨텍스트는 세션 시작 시점에 고정됨(감사자가 받은 것이
세션 시작 커밋과 일치, 그 사이 7커밋). 메인 세션도 동일해 옛 conventions.md
를 들고 있었음. 동작이 명확한 별개 경로라 conventions.md에 규칙으로 명시 —
@import 파일을 고친 세션은 기억이 아니라 파일을 직접 Read해서 따를 것
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## CLAUDE.md 분할
1537줄이라 (a) 사람이 검토 불가, (b) 공식 권장치(파일당 200줄) 7.7배
초과로 지침 준수도 자체가 저하, (c) 긴 파일 편집 시 에이전트 실수 증가.
CLAUDE.md (39줄, 진입점)
├─ @.claude/conventions.md 언어/모델 관례 + 작업 방식
├─ @.claude/project-context.md 프로젝트 설명 + 문서 구조
└─ @.claude/todos.md 지금 할 일
.claude/session-summary.md ← import 안 함(의도적, 온디맨드)
세션 히스토리 1231줄(전체의 80%)은 그 문서 스스로 "항상 읽을 필요 없음,
base/가 소스"라고 명시해온 색인이라 @import에서 뺐음. 내용 유실 없음
(1537→1647줄, 추가 헤더만큼 증가).
주의: @import는 컨텍스트를 줄이지 않음(전부 로드됨). 분할이 사는 건
사람 검토성 + 편집 정확도 + 파일 단위 자동생성 가능성.
CLAUDE.md 계열의 블록 HTML 주석은 주입 전 제거되므로 지시는 본문에 쓸 것.
## 워크플로 가짜 초록불 수정
첫 실측에서 감사 에이전트 6개 전원 실패했는데 converged:true가 나왔음
(전멸하면 fresh가 비어 "깨끗한 라운드"와 구분 불가). 감사 도구 최악의
실패 모드라 (1) 전멸이면 throw, (2) 반영 에이전트 실패 시 그 발견을
seen에서 빼 다음 라운드가 재시도하도록 수정.
## 부수
- 분할로 깨진 상호참조 20여 곳 정정(병렬 에이전트 3개).
ref-plan.md:541의 사전 존재 오류(→ pre-implementation-audit 1-5)도 정정.
- doc-check.py: 새 파일 4개를 OURS에 등록(안 하면 깨진 참조가 WARN으로만
잡힘), is_history()로 session-summary.md를 archive/와 같이 면제.
- doc-include-plan.md: 목적지가 통째로 생성되는 파일이 되면서 양방향
마커 설계의 절반(목적지 마커)이 불필요해져 단방향 생성으로 단순화.
doc-check.py ERROR 0 유지.
미해결: session-summary.md 자동생성 미착수(91개 세션 파일 마커 삽입 필요),
orphan 인용 3건(modifier-plan.md:536, v1-compat-plan.md:50,
pre-implementation-audit.md:434 — 분할 이전부터 존재).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HvEKn9f67kkx2nGLG8PsP
병렬 서브에이전트 4개로 base/research/reference/luau-test/audit/archive/root
전 영역 재감사. bind-system-plan.md 3단계 분할(1238→203줄) 이후 다른 문서가
여전히 옛 줄번호/위치를 가리키던 stale 참조 11곳을 실제 위치
(source-state-plan.md/event-plan.md/dispatch-core-plan.md 등)로 정정,
HUMAN_TODO.md의 이중 바인딩 게이트 서술을 canBound 재도입(11차 세션) 반영으로
정정, 날짜 없는 완결 주장 4건에 날짜 태그 추가. doc-check.py ERROR 0 유지.
State<Observer>/State<Effect>가 재-dispatch될 때 안쪽 값이 안 바뀌어도
Dispatch가 값 비교 없이 매번 재바인딩하던 것에 RefLeafHandler와 같은
old ~= v dedup을 추가(correctness 아니라 순수 성능 최적화 — == 비교가
매번 도는 Relate weak-table 쓰기보다 항상 쌈).
가장 큰 변경은 Tag/Attribute 핸들러 등록 모델 역전: 직전 커밋이 확정한
"TagHandler/AttributeKeyHandler/AttributeGroupHandler가 quad-base 모듈
로드 시점에 스스로 등록한다"는 결론 자체가 틀렸음이 드러남 — 이건
lifecycle-pattern.md가 이미 거부해둔 InitNamespace류 top-level 부작용
패턴과 같은 클래스였고, module-lifecycle-plan.md가 이미 확정해둔 "등록은
백엔드 팩토리가 BaseModule을 뮤테이션하는 시점" 원칙과 정면으로 어긋났음.
정정: 저 이름들은 참조 카운트/이름 claim 알고리즘 구현일 뿐이고,
HANDLER_PRIORITY_FALLBACK에 실제로 꽂히는 건 이를 감싸는 별도 이름의
TagFallbackHandler/AttributeKeyFallbackHandler/AttributeGroupFallbackHandler
— 등록 주체는 quad-base 모듈이 아니라 백엔드 팩토리. dispatch-core-plan.md/
tag-plan.md/attribute-plan.md/module-lifecycle-plan.md/architecture.md
전부 재반영, ROADMAP.md M10 체크리스트에 새 Fallback 파일 3개 추가(빠져
있으면 구현자가 만들 필요를 몰랐을 갭), 뒤집힌 원문은
archive/tag-attribute-load-time-registration-reversed.md.
/code-review를 두 라운드 돌려 findings 15건 확정 반영 — 죽어있던
Observer/Effect FALLBACK 동적 경로 가드(k 타입 미체크), bindLifetime
pseudocode의 canExecute/canBound 혼용, "결정 대기" 절이 삭제됐는데 비어
있다고 서술하던 3개 파일, "동적 경로 가드"가 볼드 텍스트뿐 실제 헤딩이
아니라 깨져있던 절 참조 6곳(###으로 승격), CLAUDE.md 11번째 세션 기록
~103줄→~13줄 압축, Tag/Attribute 정정이 프로즈만 고치고 놓친 pseudocode
2곳. 별개로 RefLeafHandler.isHandlable이 PostRef 도입 이후 안 갱신돼
`and not isPostRef(v)`를 빠뜨렸던 사전 존재 버그도 같이 발견·정정.
doc-check.py ERROR 0 유지.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
전체 .claude/ 코퍼스를 doc-check.py + 6개 병렬 서브에이전트로 감사해
stale 세션 번호, 잘못된 인용, 자기모순 배너 등 15개 파일의 실제 사실
오류를 발견·수정. 이어서 question.md 0-W(같은 Ref 객체가 두 자리에
동시에 놓이는 문제)를 선택지 (a)로 확정 — RefLeafHandler가 새 Relate
없이 bindLifetime/unbindLifetime을 재사용해 이중 배치를 방지.
이 과정에서 bindLifetime의 이중 바인딩 가드(bound 문맥)와 State emit
전파 게이팅(execute 문맥)이 서로 다른 질문인데 canExecute 하나로
뭉쳐 있던 걸 발견 — canBound를 별도 진입점으로 재도입(판정 로직은
비공개 헬퍼 하나를 공유, 코드 중복 없음). Tag/Attribute의 미지원
백엔드 처리 모델도 여러 라운드 논의 끝에 확정: TagHandler 등은
quad-base가 스스로 등록하고, addTag/removeTag/setAttribute만 백엔드
팩토리가 채우는 타입 계약 — 안 채운 슬롯은 명시적으로 에러내는 스텁.
/code-review high가 추가로 3건(gcconn-trick-verification.md 배너,
README.md 인덱스 4곳, luau-test/STATUS.md의 재작성 지침) 발견해 정정.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXqrGrh83Sh61C3tUCMdmY
- base/ref-plan.md: "PostRef" 절 신설(PreRef의 거울상 — pre-pass 공동 수집,
ProcessedPostRef 센티널+전담 Handler, 동적 경로 가드, _fired, 타입 차단).
보장 범위를 명시: 자기 서브트리 완성은 보장하되 이 인스턴스가 부모에
붙기 전에 불림(React componentDidMount와 다름).
- 복수 PreRef/PostRef의 계열 안 fire 순서를 "배열 index 순서 보장"에서
미보장으로 역전 — 구현이 아니라 계약만 좁힘
(archive/preref-order-guaranteed-reversed.md 신설).
- research/lifecycle-hooks-plan.md → base/ 승격, OnRendered 채택 반영
(마지막 열린 항목이던 채택 여부/메커니즘/스코프/패키지 전부 확정).
- dispatch-core-plan/brand/architecture/slot/modifier/typing-limits/README/
ROADMAP/question.md 전파. doc-check.py ERROR 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
워크트리(worktree-debounce-throttle-plan)에서 네 라운드로 다듬은 결과를
메인의 3단계 분할 구조에 맞춰 필요한 변경만 이식.
## 신설: research/debounce-throttle-plan.md
- Blocker가 이미 쓰는 게이티드 노드의 릴리스 트리거만 타이머로 바꾼 것.
공개 Blocker API엔 "상류 신호 도착" 통지가 없어 그 위엔 못 얹음 →
M3에서 게이트를 공용 Gate로 뺄 것.
- 두 도구의 차이는 "신호가 창 타이머를 리셋하는가" 한 비트뿐.
공개 생성자 2개 + 내부 구현 1개(초안이 옮겨온 lodash식 maxWait 공식엔
trailing 통과 직후 이중 발화 버그가 있었음).
- quad-base + 주입 op 2개: setTimeout(func, delay) -> Timeout /
clearTimeout. Roblox는 task.delay/task.cancel로 배선(인자 순서 반대).
os.clock()은 Luau 표준 라이브러리라 주입 대상 아님(diff 전용).
Timeout = { __type_timeout: true, _native: any }.
## 역전: emit은 자기 invalid 상태와 무관하게 항상 전파된다
source-state-plan.md의 "이미 invalid였다면 그 아래로 더 전파하지 않는다"가
확정된 Observer 계약(fn이 :Get()을 안 불러도 됨)과 정면 충돌 — 액면대로면
:Get() 안 하는 Observer는 한 번 울고 영구 침묵. architecture.md가 같은
다이아몬드 문제를 pull-recompute로 설명하는 것과도 어긋나 있었음.
정정 모델: invalid는 캐시 낡음 표시일 뿐, 중복 재계산은 pull-recompute+
캐시가 막고 중복 통지는 안 접음(접으려면 Blocker 같은 명시적 게이트).
- source-state-plan.md: 전파 규칙 재작성, "다이아몬드 의존성은 무엇이
푸는가" 절 신설, Observer 절 상호 참조. 플래튼 기각/:With 빌더 기각
근거를 캐시 공유로 재작성(두 결론 유지, 근거 강도는 상승)
- architecture.md, blocker-plan.md(전파를 지연시키는 유일한 요소로 위치
명문화), comparison-fusion-vide.md, framework-comparison-findings.md
- ROADMAP M0 체크리스트: 확인할 것이 정반대가 됨
- luau-test 05 → rewrite-required/(옛 모델을 통과 상태로 검증 중이었음),
STATUS.md 개수 동기화(rewrite 6→7, done 14→13)
- audit: 05 행 정정 + "12개 전원 통과"를 액면대로 읽지 말라는 경고
- archive/invalidate-dedup-propagation-reversed.md 신설
doc-check: ERROR 0 / WARN 84(작업 전 85).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
세 건을 한 커밋에 처리:
1. ui-shorthand-plan.md — Tween 지원 확정. 숏핸드가 자식 프로퍼티를 직접
대입하지 않고 Dispatch.process(child, prop, ..., 1)로 위임하면 Tween이
공짜로 따라옴(해석 코드는 PropertyHandler 하나에만 남음). "process 중
inst를 바꾸는 건 키를 바꾸는 것과 같은 층위라 UB 아님"을
dispatch-core-plan.md에 일반 규칙으로 명문화. wrap을 Tween<T>.Value에만
적용되도록 들어올리는 헬퍼가 새로 필요한 유일한 부품. ROADMAP M10에
통째로 빠져 있던 UI 숏핸드 항목도 보강.
2. existing-instance-bind — 기각, research/ → archive/. 사유: Length/Offset
등 quad가 만든 트리를 전제한 부기를 바깥에서 밀고 당기는 버그 표면이
치명적으로 넓어짐. "열려 있음"을 전제로 쓰인 본문 7곳도 같이 정정
(architecture.md의 "아직 미정" 절은 유일 항목이었어서 절 자체를 갱신).
3. bind-system-plan.md 3단계 분할 + store-semantics.md 흡수(순수 이동):
- base/store-plan.md 신설 — Store = 이름 붙은 Source 모음
- base/source-state-plan.md 신설 — 반응형 코어(Source⊇State, 전파 모델,
:With/:Compute/:Apply/previous, Observer, 구독·생명주기 게이트)
- bind-system-plan.md 1238→203줄(인스턴스 생성·이벤트 네이밍 + 색인)
- store-semantics.md 삭제
참조 40여 곳 스윕. doc-check.py ERROR 0, WARN 101→84.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
canExecute(inst,value) 2-인자 시그니처를 폐기하고 canExecute(value)로 정정.
2-인자는 증상이었고 원인은 2026-08-08 세션이 .Subscribed에 "leaf 바인딩
생존"이라는 두 번째 의미를 겹쳐 얹은 것 — .Subscribed는 전역 :Subscribe()
전용 필드이고 bindLifetime과 무관함. bindLifetime이 바인딩 시점에 inst의
gcconn 참조를 value 쪽 Relate로 복사해두면 생존을 value 하나로 물을 수 있음.
- canBound(handle) 폐기 → canExecute(value)로 통합(이중 바인딩 게이트 겸함)
- unbindLifetime도 1-인자로 축소 — 호출부가 _mountedInst를 되짚을 필요 없어져
"홀더가 갈아치워지면 해제가 빗나가는" 잠재 버그 클래스 소멸(slot-plan 5곳)
- gcconn/gchold를 lazy 생성에서 Instance 생성 시점으로 전환, 클로저가 inst까지
캡처 — Instance userdata 포인터 동일성은 inst-키 Relate 전체의 전제였음
(relate-plan에 "전제" 절 + "안전히 유지되면 항상 SetWeak" 일반 규칙 신설)
- canExecute의 실제 호출부를 State 전파 루프로 명시(구독자 weak + 발화마다
게이팅) — 이게 코드로 한 번도 안 적힌 게 오류가 여섯 세션 살아남은 이유
역전 원문/오염 경로/교훈은 archive/canexecute-inst-arg-reversed.md.
luau-test/10은 폐기된 모델을 검증 중이라 rewrite-required/로 이동.
부수: 3~4차 세션이 남겨둔 CLAUDE.md 세션 히스토리 항목과 4차 세션 로그
파일도 미커밋 상태여서 같이 실림. doc-check ERROR 1건(CLAUDE.md:1156 →
session/2026-08-14-03-lifecycle-hooks-plan.md)은 그 파일이 디스크 어디에도
없어서 남음 — 3차 세션 쪽에서 채워야 함.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`question.md` 최우선 두 건을 한 패스로 닫고, 배너를 달고 있던 7개 문서 +
인덱스 레이어 전체를 갱신했다.
0-Z (Attribute 이름 소유권):
- 그룹은 비공개 `GetKey`로 이름마다 자기 전용 키를 써서 위임 → 교차 오염
구조적 제거. 이름 소유권은 `AttributeKeyHandler`의 이름 claim이 판정
(`nameClaims` Relate, 다른 키가 같은 이름을 노리면 즉시 error).
- 권고안 (a)(그룹 안 claimant Relate)는 그룹↔직접 쓰기를 못 잡아 기각 —
두 경로가 만나는 말단 핸들러에서 공개 키는 같은 객체라 소유자 구분 불가.
0-A (재디스패치 = 하강 diff):
- 래핑 핸들러의 선행 `retractFrom` 폐기, `Dispatch.process`가 슬롯의
`handler`를 먼저 비교(같으면 클로저에 새 값 전달 후 재process, 다르면
그 자리부터 전량 철거).
- 귀결: `retractFrom`이 3-인자로 축소(힌트를 외부에서 만들어 넣을 자리
소멸), `isX(hintValue)` 가드 규칙 폐지, 깊은 체인 힌트 유실 캐비엇 삭제,
Dispatch의 점유 체크 폐지.
- 9차 세션이 미뤄둔 2단계 분할을 같이 수행 — 디스패치 코어를
`base/dispatch-core-plan.md`로 분리하며 재작성(bind-system-plan은
2263→1219줄). 옛 모델은 archive/dispatch-hintvalue-model-reversed.md.
패키지 재배치 (사용자 제기):
- Tag/Attribute의 부기 알고리즘 전체를 quad-base로, 백엔드는
`addTag`/`removeTag(inst,{string})`/`setAttribute(inst,name,v)` 3개 op만
주입(웹 className/data-* 대응). 엔진 고유 타입 패밀리만 백엔드.
- `HANDLER_PRIORITY_FALLBACK` 신설 — base 제공 핸들러의 밴드, 백엔드가
평범한 우선순위로 덮어쓰면 언제나 이김.
부수: 스파이크 04/19가 옛 모델을 검증 중이라 rewrite-required로 이동,
question.md 최우선 칸 비움, 새 소소 항목 2건 등록(Merged 이름 중복,
`hintValue` 이름 재검토). doc-check ERROR 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
9차 세션의 구조 변경(luau-test 재편/bind-system-plan 분할/question.md
트림)이 남긴 반영 누락을 6개 병렬 에이전트로 찾아 즉시 수정:
- luau-test 재편 후 깨진 flat 경로 참조 9곳을 파일명+실제 폴더로 정정
- bind-system-plan.md 분할 후 자기참조/외부참조 깨짐 8곳 정정
- ref-plan.md에 0-Z 배너가 안 옮겨와 옛 재디스패치 모델을 무배너로
서술 중이던 것 발견 — 배너 추가, 반영 대상 6개→7개로 갱신
- "8차 세션"으로 잘못 표기된 9차 세션 작업 17곳(git 커밋 타임스탬프로
교차검증) 정정
- question.md 트림 중 빠진 열린 질문(State<State<T>> 평탄화) 복원,
트림 후 깨진 참조 2곳 정정
- ROADMAP.md M0 섹션에 0-Y/0-Z 게이트 표시 누락 보강
doc-check.py ERROR 0 유지. 새로 연 설계 질문 없음 — 전부 기존 서술
정합성 문제.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
사용자 지적: 2989줄이라 검토가 불가능하고 한 곳의 실수가 미치는 범위가
너무 큼. 특히 "이벤트 연결은 다른 base 문서가 되어야 할 듯"이라고 직접 지목.
**1단계 (완료, 2989 → 2263줄)** — 인바운드 참조가 적고 자기 완결적인 것부터:
- base/ref-plan.md (509줄) — Ref/PreRef 전체
- base/event-plan.md (103줄) — 이벤트 self 미전달, false로 disconnect
- base/brand-plan.md (137줄) — Brand 런타임 nominal 판별
내용은 **순수 이동, 결정은 하나도 안 바뀜**. 원위치엔 포인터 스텁을 남기고,
이 절들을 지목하던 인바운드 참조 20곳을 새 파일로 갱신(잔존 0 확인).
README 색인에 3개 행 추가.
**2단계는 의도적으로 미룸** — 남은 디스패치 코어(~1000줄)/반응형 코어
(~950줄)는 0-Z 확정 시 어차피 전면 재작성 대상이라, 재작성하는 그 패스에서
파일을 가르는 게 총 변경량과 실수 위험이 모두 작음(인바운드 ~37곳을 한 번만
고침). dispatch-redispatch-diff-plan.md 6절에 그 지시를 명시했고,
bind-system-plan.md 헤더에도 분할 현황표를 남김.
부수: 이벤트 *네이밍* 관례는 인스턴스 생성과 한 절에 섞여 있어 쪼개면
양쪽이 반토막 나므로 원위치 유지(event-plan.md 헤더에 명시).
luau-test/README의 "에이전트가 직접 실행 못 함" stale 서술도 정정.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
4차 감사(반복되던 두 패턴을 grep으로 전수 스윕)에서 발견한 것 정리:
- architecture.md: 모든 세션이 "먼저 읽으라"는 진입점 파일인데 소스
트리의 chains/retractFrom 서술에 0-Z 포인터가 전혀 없었음 — 최상단
배너 추가(가장 중요한 gap, question.md 0-A가 경고하던 바로 그 함정)
- tween-plan.md/operator-sugar-plan.md/effect-plan.md: self-lazy-핸들
계약(0-Y)에 의존하는 실행 예시/메커니즘 서술에 포인터가 전혀 없었음
— 3곳 추가
- CLAUDE.md: "지금 할 일" 0번(0-Y/0-Z가 최우선 게이트)과 1번("M0 착수
전 남은 유일한 게이트는 luau-test뿐")이 정면 모순 — 0-Y/0-Z 발견
이전에 쓰인 문장이 안 갱신된 stale, 정정
- slot-plan.md 3곳/additional-primitives-plan.md/
dispatch-redispatch-diff-plan.md: reconcile/Slot:List/재귀 재emit
가드 설명이 여전히 "파괴"를 현재형으로 서술 — 2026-08-13 여섯 번째
세션의 언마운트 전환 반영해 정정(지난 세 라운드가 놓친 인스턴스들,
전수 grep으로 확인)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
## luau-test/STATUS.md 신설 (사용자 요청)
"사람이 보기엔 뭐를 바로 봐야하는지 눈에 안 띄어서" — 파일별 상태를
🔴 사람 결정 필요 / 🟠 스파이크 깨짐 / ⚪ 미실행 / ✅ 통과로 분류하고
봐야 할 것을 위에 배치. 역할 분담도 명시: STATUS.md가 상태의 소스,
README.md는 각 파일의 검증 의도·배경, audit/는 실행 결과 상세.
## 코퍼스 전반 감사 (에이전트 둘, 보고받은 것 전부 직접 사실 확인 후 수정)
가장 위험: ROADMAP.md가 최우선 게이트(question.md 0-Z, 하강 diff 재설계)를
전혀 안 짚고 있었음. base 4개 문서엔 ⚠️ 배너를 달아뒀는데 정작 구현 순서의
소스인 ROADMAP엔 없어 M2/M4/M10 담당자가 옛 모델로 구현할 위험이 실재.
→ 세 마일스톤 전부에 배너/포인터 추가.
두 번째: slot-plan.md 앞부분이 뒤집힌 결정을 여전히 "확정"으로 자칭.
"폐기, 옮기지 않음, portal은 오버엔지니어링"과 "열린 질문" 절에 역전 표시가
없어 앞에서부터 읽는 구현자가 구 모델로 짤 위험 → 🔄 역전 배너.
더 심각하게 reconcile 의사코드가 여전히 rawRemove(파괴)를 부르고 있었음
(같은 문서가 "[반영 완료]"라 태그해둔 것과 정면 모순) → rawUnmount 신설.
filter/toggle 근거 절도 "제거=파괴" 전제라 캐비엇 추가(결론은 유효).
그 외:
- ROADMAP M11 Tween이 이미 확정된 넷을 미결로 둠(override 정책/옵션 값
모양/Animate 시그니처/initValue) + research/tween-plan.md 죽은 링크 4곳
- bind-system-plan.md:328, architecture.md:213의 "4종 계약" 잔여
(isHandlable(k,v) 구식 시그니처 포함)
- store-semantics.md의 "State는 가칭" stale — 2026-08-12에 최종 확정됨
- luau-test/README.md의 04/19 판정 기준이 재작성된 파일을 못 따라감.
특히 19 C섹션 기준이 정상 동작(nested error)을 실패로 오판하게 돼 있었음
- documentation-content-map.md의 4종 계약 / Attribute<T> 옛 이름
- modifier-plan.md의 "정정이 원문에 소급 안 됨" 패턴
- attribute-plan.md "이름 소유권"이 0-Z 미결인데 "최종"이라 적힌 것 캐비엇
- relate-plan.md의 kSlotMap 역할 서술 부정확, 상호참조 방향 오류 2곳
판단해서 안 고친 것: effect-plan.md의 "cleanup" 14곳 — Handler retract
(내부 배관)와 달리 Effect(fn)에서 사용자가 작성한 fn이 반환하는 React식
콜백이라 다른 층위. 매 감사마다 재지적되므로 lifecycle-pattern.md에
"확인 완료, 의도된 별개 개념"으로 못박음.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c33ae04(인덱스 기반 Dispatch 재설계) 전체를 직접 정독. 방향은 옳으나 새로 쓴
의사코드가 손 트레이싱을 안 거친 채 커밋됐음이 드러나 실제 버그 4건 발견·수정:
1. Dispatch.process가 chains:SetStrong을 h.process 뒤에 둬서 최초 마운트에서
하위 위임 retractor가 통째로 유실(재귀가 자기 테이블을 만들었다 바깥이
덮어씀 — Slot이면 서브트리 중복 마운트로 직결). SetStrong hoist + 재귀 중
hole 방지용 no-op 점유 마커 추가.
2. Attribute 그룹이 process에서 retractFrom을 선행 호출해 인덱스 1이 무조건
비워지는 바람에 점유 체크(=소유권 충돌 감지)가 전혀 작동 안 함. 철거를
반환 클로저로 이동.
3. SlotHandler가 claim 실패에도 파괴적 클로저를 반환 — nested는 엄격
claimOwner, top-level은 claimOwnerAt(inst,k)로 분리. rawRemove의
releaseOwner 누락, destroySlotTree의 GC 타이밍 의존 오류도 수정.
4. Ref retractor가 spurious 재발행에서도 relate를 지워 dedup 무력화.
사용자 설계 결정:
- State<Slot> 교체 = 파괴가 아니라 언마운트(state<Frame>와 동일). 포탈이
별도 기능이 아니라 이 결정의 귀결이 됨. 해제는 setOffsetSource(None) →
setLength(0) 순서 고정(반대면 죽는 중인 서브트리 Source에 헛된 Set이
날아감). dispose(value) 신설 — 트리가 아직 요구하면 파괴 거부하고 error.
- 재디스패치를 "하강 diff"로 재설계 — 래핑 핸들러의 retractFrom 선행 호출을
폐기하고 Dispatch.process가 핸들러를 먼저 비교. 힌트가 None/래퍼로 오염돼
깜빡임 방지가 조용히 꺼지는 결함이 계기. 아직 research/에만 있고 base
미반영(Attribute 이름 소유권 하나가 남음) — base 4개 문서에 경고 배너.
전파 누락 정리: ROADMAP.md가 2026-08-08 이전 모델로 남아있던 것, base 내
"3종 vs 4종 계약" 모순 6개 문서, luau-test/04가 없어진 가드를 검증하던 것
(04는 인덱스 모델 + 버그 1 재현 음성 대조군으로 전면 재작성).
재발 방지: bind-system-plan.md "Handler 작성 체크리스트"(7항목),
relate-plan.md "언제 Relate를 쓰고 언제 쓰면 안 되는가" 신설.
다음 세션 최우선: question.md 0-Z(Attribute 이름 소유권) — 사용자가 직접
스케치하며 심층 분석 예정.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
네 번째 세션: State<State<T>> 발견 방식을 서브에이전트로 코퍼스 전체에
반복해 Tag 참조 카운트(객체 identity 키잉)/Attribute 그룹 자기충돌 버그
발견·수정. Attribute 소유권 충돌 방지를 위해 처음엔 Dispatch.processAs/
retractSelfAndUnder 체크포인트 핸들러 패턴을 신설.
다섯 번째 세션: 체크포인트 패턴이 임시방편이라는 지적에서 출발해, chains의
핸들러 객체 identity 기반 추적 자체가 State<State<T>>를 UB로 만든 근본
원인이었음을 재확인 — 재귀 깊이 인덱스로 재설계(같은 키 재귀는 index+1,
다른 키 위임은 항상 1부터), Handler 계약을 process/retract 2-메소드에서
process가 자기 retract 클로저를 반환하는 1-메소드로 축소. 이걸로
State<State<T>>가 UB에서 정상 지원 대상으로 바뀌고, 전날 만든 체크포인트
패턴 전체가 불필요해져 archive로 이전. 여러 핸들러(StoreBind/Ref/Tag/Slot/
Attribute)의 private Relate 상태 저장소도 대거 정리됨.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132hkscPQWa34iyHrHdoRiF
- Attribute: AttributeKeyHandler.retract를 완전 no-op으로 재정정 —
지우는 건 오직 명시적 None/nil(process)로만, 그룹이 사라진 이름을
자동으로 SetAttribute(nil) 안 해줌(Ref의 "Destroy 무관" 철학과 통일).
단 사라진 이름의 StoreBind 구독은 retractUnder로 끊어 리소스 누수 방지.
- Slot: slotOwner를 elementOwner로 일반화해 top-level Dispatch/nested
Add 경로가 같은 소유권 레지스트리를 쓰도록 통합(이중 마운트 gap 폐쇄).
bindLifetime을 top-level 전용으로 축소(nested는 _elements 강참조로
transitively 생존).
- 일반 규칙 신설(bind-system-plan.md): retract의 v는 타입 미보장이라
내용을 보려면 isX(v) 가드 필수, retract 안에서 process 호출은
retractUnder 체인 추적을 꼬는 UB.
- and/or 삼항 관용구 전면 금지(기존 "항상-truthy면 예외" 조항 폐기),
코퍼스 전체 실제 코드 6곳을 if-then-else로 교체.
- 7-에이전트 코퍼스 감사로 stale 서술 다수 정정: retract-always-fires
정정 전파 누락(bind-system-plan.md Tag 예시), Tween research→base
승격 반영 누락(architecture.md/ui-shorthand-plan.md), Relate API
인자 개수 버그(slot-plan.md), pre-implementation-audit.md 열린 항목
개수 오류(question.md/CLAUDE.md).
- 백로그: Attribute 자동 unset용 :Apply 유틸 아이디어를
research/operator-sugar-plan.md에 추가(착수 안 함).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
Completed 시 per-instance 북키핑 정리는 불필요 — 자연완료는 유저가 원한
목표값에 도달한 상태라 남은 참조가 부작용 없고, Value가 lerp 가능한
프리미티브라 메모리 문제도 없어 별도 정리 장치는 오버엔지니어링. 이걸로
tween-plan.md에 남은 열린 질문이 없어져 research/에서 base/로 승격,
라이브 크로스레퍼런스 전부 갱신.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sketch Sum/Not/Product-style :Compute/:Apply sugar (research/operator-sugar-plan.md,
implementation deferred). Reusable curried combinators (Sum, Animate) must
go through :Apply, not :Compute — quad rejected implicit auto-tracking, so
a factory's closed-over deps only register if the factory re-declares them
via self:Compute(...) internally; plugging a pre-built factory straight
into :Compute silently drops reactivity. Flip Animate's call site
accordingly in tween-plan.md, add the convention to bind-system-plan.md's
:Apply section, and fix a stale two-arg Animate signature comment in
architecture.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CanAnimate: State<boolean>|boolean|nil (nil defaults true) lets Animate
bypass wrapping in Tween{} entirely, covering the reduceMotion use case
natively. Also documents that Luau's if-then-else expression and const
bindings are official syntax (not hallucinated), so agents don't revert
them; const adoption deferred pending tooling support.
Adds Attribute(store1, store2, ...) as a Tag-shaped array-part value
object that projects one or more Stores' named Source fields onto
native Roblox attributes in one binding, with .Merged for combining
heterogeneous Stores. Rejected: a bare [Attribute] = Store{...} hash
slot (only one slot per instance, can't merge heterogeneous stores)
and making Attribute a Store subtype (would let Store<T>'s T be a
Store again, colliding with the existing "handler-layer values can't
live in Source" rule). Verified the value-slot aggregation in .Merged
doesn't reopen the earlier "layered Store" rejection (archive/
context-rejected.md) since it's an explicit one-time author-time
composition, not an implicit read-time parent-chain fallback.
Renamed the existing single-key constructor Attribute<<T>>(name) to
AttributeKey<<T>>(name) to remove the name collision with the new
group primitive (provisional per existing OnChange/OnChangeKey
precedent; final bikeshed still queued in question.md).
Follow-up: AttributeKey(name) now memoizes through a name-keyed weak
table, guaranteeing AttributeKey(a) == AttributeKey(a) while any
strong reference (e.g. a live Dispatch chain entry) keeps it alive.
This let the group Attribute handler drop its originally-planned
self-contained SetAttribute/subscription logic and instead recurse
into the existing single-key AttributeKey dispatch path per field,
reusing its None/retract/store-bind handling instead of duplicating
it — the group handler's own state shrinks to just a prior-name-set
diff for deciding which keys to retractUnder. The same memoization
was applied to OnChangeKey for the same reason (pure name-to-key
mapping, no other varying state), confirmed safe even once
State<function> callbacks are involved.
Reflected across base/attribute-plan.md, base/onchange-plan.md,
base/architecture.md (source tree + Brand isX list), ROADMAP.md M2/M10,
question.md, README.md, and the luau-test type-narrowing spike.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
이벤트 문자열 키 패턴이 GetPropertyChangedSignal엔 안 통해서(프로퍼티 이름이
값 세팅 키와 겹침) 별도 OnChange(name) DI 키를 신설. Attribute와 달리 제네릭
타입 파라미터 없이 콜백 타입은 인라인 명시 - 이벤트 바인딩과 같은 급의 타입
안전성 트레이드오프. 전부 quad-roblox 소속, State<function>은 기존 이벤트
store-bind 메커니즘 재사용. 프로퍼티별 정적 코드 생성 안은 규모 폭발로 기각.
독립 Dispatch 핸들러("v가 Store인 아무 k나 잡는 우선순위 최상위 핸들러")
모델을 PropertyHandler가 소비하는 값-레벨 래퍼(Tween<T>)로 전환. State/
Source 언랩(범용 StoreBind)과 "이 값이 트윈 대상인가" 판단을 분리해,
일반 반응형 프로퍼티 바인딩이 Tween 파일을 거쳐가는지 불명확했던 구조적
모호함(pre-implementation-audit.md 1-1)을 해소.
- Tween.Value는 plain T만(반응성은 바깥 :Compute가 전담, 이중 경로 방지)
- hasBeenSet+활성 엔진 트윈을 3-상태 릴레이션 슬롯(RobloxTween|true|nil)
하나로 통합, 첫 세팅은 항상 애니메이션 없이 스냅
- 활성 트윈 정리 후에만 새 값 세팅(순서 뒤바뀌면 값이 덮어써질 위험)
- 타입은 T'=T|Tween<T> 치환만으로 기존 T|State<T> 모양에 자동 통합
- useTween은 :Apply(Animate(...))로 해소, 새 옵션 필드 불필요
- PropertyHandler가 항상 매치되는 유일한 핸들러가 되어 Tween↔프로퍼티
handler-switch에 의존하던 retract 케이스가 사라짐
구 모델은 archive/tween-special-bind-key-reversed.md로 보존. 코퍼스
전체(bind-system-plan.md/architecture.md/modifier-plan.md/ROADMAP.md/
question.md/README.md/attribute-plan.md)의 stale Tween 참조 동기화,
CLAUDE.md 세션 요약 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add/Remove→Added/Removed, Merge→Merged와 같은 분사형 네이밍 컨벤션을
Override에도 적용하되, override가 불규칙동사임을 반영해 정확한 과거분사
Overridden을 채택(Overrided는 오기). question.md 용어 재검토 목록에서
제거하고 관련 base/research 문서 전반에 반영.
Tag를 해시 파트 boolean DI 키에서 Modifier식 immutable clone 체이닝 값
객체로 재설계(구 모델은 archive/tag-hash-key-model-reversed.md로 보존).
이 과정에서 재귀 재-dispatch(StoreBind/Tween/NoneHandler)의 retract가
다단 체인까지 정확히 전파되지 않던 설계 공백(pre-implementation-audit.md
1-2번)이 드러나, Dispatch가 (inst,k)별 핸들러 체인을 직접 소유하고
retractUnder로 꼬리부터 정리하는 방식으로 해결.
- Relate(inst-weak 릴레이션, SetWeak/GetWeak/SetStrong/GetStrong, 비싱글톤)
신설 — base.perInstanceState(inst) placeholder를 정식 대체
- bindLifetime(inst,value)/canExecute(inst,value) 탑레벨 함수로 확정,
Relate 위에 구현(gcconn/gchold), LifetimeHandle.bind식 네임스페이싱 기각
- canExecute 시그니처를 (handle)에서 (inst,value)로 재정정 — Observer 자신의
Subscribed 상태를 먼저, inst의 gcconn.Connected를 그 다음 확인
- store-bind 재실행 구독 메커니즘 = state:Observer(fn):Subscribe() 재사용으로 명문화
- 핸들러 계약: retract 필드는 no-op이라도 항상 정의(생략 시 핸들러 교체
순간 크래시) — 확정
- question.md/ROADMAP.md/architecture.md/README.md 전체 동기화
CreatedRef 이름 완전 폐기 — Ref(default)/PreRef(default)가 이미 Compose식
Type(args) 팩토리 생성자로 확정돼 있어 별도 래퍼가 불필요했음(2026-08-04
Ref 일반화 이전 시절의 잔재). bind-system-plan.md/ROADMAP.md/question.md/
architecture.md/documentation-content-map.md 전체 동기화.
PreRef pre-pass 구현 위치 확정 — 새 Dispatch.* 함수나 flatten에 얹지 않고
이미 확정된 Dispatch.drive(inst, flattened) 자신이 두 번 순회(pre-pass +
정상 두 패스)하는 것으로 확정. flatten에 얹는 안은 재바인드 시 flatten
재호출 가능성과 충돌해 기각. 복수 PreRef는 배열 index 순서, 동적 경로로
도착한 PreRef는 전용 Handler가 즉시 error.
소진 슬롯을 nil이 아니라 None으로 — 사용자가 Luau REPL 반례로 직접 발견:
키가 촘촘한 저범위 정수에서 벗어나면(nil 구멍 포함) 순회 순서가 index
오름차순을 안 지킴, table.insert가 쓰는 #t도 구멍 있는 테이블에서 정의
안 됨. Ref 콜백/대기자 배열과 PreRef pre-pass 둘 다 None 소진으로 정정.
배열 파트 None(순수 스킵)과 해시 파트 None(NoneHandler 경유)이 다른
경로임을 명시.
props.Modifier/props.Ref forwarding에 `or None` 필수 관용구 확정 —
nil-hole 위험도가 국소적이지 않고 테이블 전체에 영향을 준다는 게 이번
실측으로 드러나 방어 필요, 기존 None 스킵 메커니즘 재사용이라 새 코드
불필요.
부수 발견/보강: Modifier() 바닥 생성자가 문서에 없던 갭 보강, Brand 태그
목록에 RefTag/PreRefTag/ModifierTag가 빠져있던 갭 보강(isRef/isPreRef는
isState와 달리 단순 항등 — PreRef가 일반 Ref 핸들러에 안 잡히려면 필수).
archive/agent-mistake.md 관례에 따라 이번 세션의 실수(Modifier.Rounded(8)
stale 치환 시 잘못된 예시로 대체)는 CLAUDE.md 세션 로그에 경위 그대로
남김.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
여러 세션에 걸쳐 쌓인 stale 참조/자기모순을 서브에이전트 병렬 감사로
찾아내 전부 수정:
- bind-system-plan.md: CreatedRef {phase=...} 옵션이 폐기 이후에도 두
곳에 방치돼 있던 것을 archive 포인터로 정리
- question.md: Ref 이름 재검토 대상 여부 자기모순 해소, framework-
comparison-findings.md/v1-compat-plan.md §8 누락 항목 보강
- UICorner 숏핸드 개명(구 Modifier.Rounded(8))을 modifier-plan.md/
store-semantics.md/ui-shorthand-plan.md/documentation-content-map.md/
pre-implementation-audit.md 5곳에 전파
- canExecute(handle) 시그니처 정정을 bind-system-plan.md/
store-semantics.md 예시 호출부에 전파
- architecture.md/ROADMAP.md/CLAUDE.md의 stale 문구·누락 참조 정정
- store-semantics.md 제목을 "State는 Source 위의 캐시 레이어"로 정정
(Store 아님 — 사용자 확인)
archive/agent-mistake.md 신설 — 설계 반전/기각과 구분되는 세 번째
카테고리로, 에이전트가 문서 작성 중 스스로 낸 개념 혼동을 같은 세션
안에서 정정한 사례(canExecute/isHandlable 혼동, isSource 오판) 전용.
CLAUDE.md 세션 로그의 중복 서술을 옮기고 포인터만 남김.
slot-plan.md의 CRUD 의미론 갭은 사용자 요청으로 이번 라운드에서 보류.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
세션 clear 전 정합성 점검 — 이미 커밋된 결정들이 다른 문서에 제대로
퍼져있는지 확인하고 빠진 곳을 보강:
- ROADMAP.md M3에 Observer(즉시실행 확정)/Effect(fn, state?) 체크박스
추가(그동안 base 문서에만 있고 로드맵엔 전혀 없었음), M8 Ref 체크박스를
:Set/:Callback/:Wait API + 파일 분리 + resume payload 정정 내용으로 갱신
- base/architecture.md 소스트리에 Modifier.luau/Blocker.luau/Effect.luau가
통째로 누락돼 있던 것 추가(Ref.luau 코멘트도 최신 API로 갱신은 이미 완료)
- README.md의 effect-plan.md 요약이 "Observer와 관계 미해결"로 남아있던 것
정정
- documentation-content-map.md의 "아직 문서화 보류" 목록에서 해소된
Effect/Observer 항목 제거
- CLAUDE.md 다섯 번째 세션 절에 당시 기록 안 됐던 Override 서브타입
미검증 이슈(modifier-plan.md 9-2번) addendum 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
base 밖으로 늘 읽을 필요 없는 참고자료(quad-v1-architecture, comparison-fusion-vide)를
새 reference/ 폴더로 분리하고, ui-shorthand-plan을 base로 승격(RoundSize 드롭+
UICorner/UIPadding/UIScale 리네임), additional-primitives-plan을 Blocker/Effect(base
승격)·Batch/Context(archive 기각)·키 기반 컬렉션 재조정(research 잔류)으로 4분할했다.
component-composition-plan의 중복 역전 서사는 기존 archive 포인터로 압축하고, archive
제목 컨벤션을 [역전됨]/[기각됨]로 분화했다. tween-plan에는 retract/canExecute 구분
메모와 트윈 옵션 값 모양 논의를 추가했다. Effect가 Observer 변형인지는 임의로
결론내지 않고 question.md에 열린 질문으로 남겼다.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- CreatedRef의 {phase="created"|"mounted"} 옵션 폐기, 위치 기반 순서로 대체
- base 디스패치가 배열 파트(children/Ref)를 해시 파트(프로퍼티/이벤트)보다
먼저 처리하도록 명시적으로 두 패스 계약화
- PreRef 신설: 프로퍼티/이벤트보다도 먼저 채워져야 하는 케이스(Roblox
ChildAdded/DescendantAdded/Changed의 동기 발화 대응) 전용, Modifier/
Store 타입 차단 + 위치 무관 호이스팅
- Ref 콜백/대기자 실행 구현 디테일(coroutine vs function 분기) 추가
- 역전된 원 서술은 archive/ref-phase-option-reversed.md로 보존
- architecture.md/question.md/documentation-content-map.md/ROADMAP.md 동기화
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
State/Source의 값 읽기 접근자에서 .value(관용 표기)를 제거하고 :Get() 하나로
통일 — "관측해야 실체화된다" 원칙이 가장 날카롭게 느껴져야 할 지점에서
프로퍼티 문법이 그 느낌을 무디게 한다는 판단. .value 표기 자체는 폐기가
아니라 Ref 전용으로 좁혀짐(Ref는 lazy가 아니라 읽어도 계산이 안 트리거되므로
프로퍼티 문법이 정직함) — 이름 충돌 자체가 사라짐.
bind-system-plan.md/store-semantics.md/architecture.md/debug-tooling-plan.md
전체의 .value 언급을 :Get()으로 갱신, question.md의 관련 열린 질문은
완전히 해소되어 제거.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
여러 base 문서(store-semantics.md, bind-system-plan.md, architecture.md)에
"store.key는 Store 생성 시 이미 만들어둔 Source를 그대로 반환할 뿐"이라는
eager-only 서술이 반복돼 있었는데 부정확했음 — Luau 타입은 런타임에
강제되지 않고 defaults도 선택이라, Store<<T>>() 처럼 defaults 없이 만든
뒤 .Key:Set(v)를 부르면 eager 생성만으론 .Key가 nil이라 크래시남.
정정된 모델: Store 생성 시점의 eager 생성(각 defaults 키마다 미리 Source
생성, 여전히 필요) + store.key 접근 시점의 lazy __index 생성(아직 없는
키를 그 자리에서 만들어 저장, 재접근시 재생성 없음) 둘 다 필요.
bind-system-plan.md에 남아있던 관련 stale 서술도 같이 정정:
- "__newindex/__index 프록시로 감싸면 됨"은 이후 :Set() 전환으로 무효화됨
- "defaults 테이블 직접 mutate는 UB"는 최신 모델과 안 맞음(defaults는
라이브 백킹이 아니라 아직 안 만들어진 Source의 초기값 템플릿일 뿐이라
나중에 바꿔도 문제없음, UB 아님)
question.md에 .value vs :Get()/:Pull() 읽기 접근자 이름 재검토 항목도
추가(아직 미결정 — Finalize는 기존 cleanup 계열 어휘와 충돌해 기각,
Pull과 Get/Set 대칭 사이에서 검토 중).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4개 서브에이전트로 base/ 전체 + 근접 research/를 모호성/지연결정리스크/
단순화후보 세 렌즈로 재감사. 가장 구조적인 발견은 Tween.luau가 문서
전체에서 "범용 store-bind 캐치올 핸들러"의 유일한 예시로 서술되는 문제
(일반 반응형 프로퍼티 바인딩이 실제로 Tween 파일을 거쳐가는지 불명확).
그 외 props.Modifier/Ref forwarding의 nil-hole 함정, canExecute 실제
구현 미확정, LifetimeHandle 로드맵 순서 역전 등 우선순위1급 11개 +
우선순위2급 11개 + 단순화후보 2개.
부수적으로 architecture.md 소스트리의 stale 주석(Store.luau/Ref.luau)
정정, question.md/README.md에 이 감사 반영, CLAUDE.md에 세션 요약 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 6차 라운드: 태그 네임스페이싱(Ref로 충분), Store가 Store를 담지 않음 확정
- Modifier 메커니즘 전체 확정(정적 merge, immutable+clone 체이닝, State
필드 지원, "관측해야 실체화된다" 전역 원칙) — base/modifier-plan.md 신설
- 컴포넌트화 논의 시작(research/component-composition-plan.md) — 컴포넌트=
플레인 함수, State/Source 읽기·쓰기 경계, StoreSource 프록시까지 수렴,
modifier/Ref의 컴포넌트 경계 통과 방식은 열린 채로 남김
- .claude/ 코퍼스 전체(약 15개 문서)를 서브에이전트로 감사해 여러 라운드에
걸쳐 쌓인 모순/중복/stale 마커/끊긴 참조 다수 수정
- purity-and-effects-plan.md를 research/에서 base/로 승격
- CLAUDE.md: 라운드별 인수인계 메모 3개를 하나로 통합, 오래된 "더 이상 열린
질문 없음" 모순 제거
- question.md: 시간순도 우선순위순도 아니던 구조를 "지금 열려있는 것" 중심
으로 재정리, 용어 정리 제안(State/DI/PerInstanceState 등 우선순위) 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>