quad/.claude/base/slot-plan.md
qwreey 5c9d10df66
docs: .claude/ 코퍼스 정리 — reference/ 신설, 승격/기각 분리, 역전 이력 트리밍
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>
2026-08-07 14:39:20 +09:00

9.1 KiB

Slot — 뮤터블 자식 배열, 엄격한 단일 마운트 소유권 (base로 승격됨)

상태: base — 설계 방향(소유권 귀속, 재마운트 시 throw, retract=폐기)과 소스 트리 상 패키지 경계까지 확정되어 research/에서 승격됨(base/ architecture.md의 "구현 착수: 소스 트리 구조 확정" 절 참고). 원본: .claude/initreq/raw-userinput.md "slot을 구현하도록 하기로 했음" 절. Fusion의 Children SpecialKey와 Vide의 mount 무가드 비교는 reference/comparison-fusion-vide.md 참고 — 결론: 두 라이브러리 어디에도 이런 엄격한 단일 마운트 가드가 없음, quad의 진짜 개선점.

base/roblox 패키지 경계 (2026-08-04, 5차 라운드 확정)

Slot의 add/remove/clear 재조정 로직(추상 자식 참조 기준 — "이 자리에 뭐가 있어야 하는가"를 결정하는 순수 로직)은 quad-base/src/Dispatch/Slot.luau가 소유. 실제 트리 조작(Instance Parent 설정/Destroy)은 quad-roblox/src/ Handlers/Slot.luau가 그 위에서 적용/해제만 담당 — 다른 모든 인터페이스/구현 분리와 동일한 패턴(base/architecture.md의 소스 트리 참고). Slot 자체는 당연히 Instance들을 담게 될 것으로 취급.

추가로 필요해진 핸들러: Slot과는 별개로, k가 number이고 v가 이미 만들어진 Instance인 경우(중첩 인스턴스를 자식으로 직접 넣는 경우, 예: Frame { Frame {} })를 위한 핸들러도 필요 — quad-roblox/src/Handlers/ InstanceChild.luau. Slot은 "뮤터블 배열"을 다루고 이 핸들러는 "정적으로 하나 박아넣는" 더 단순한 경우라 별개로 둠.

개념

add/remove/clear/get/set 등 뮤터블 연산을 지원하는 메타 배열. 실제 바인드가 일어나면 child로 풀리고, 이 메타 배열에 CRUD를 하면 실제 children이 적절히 제어됨.

핵심 제약: 소유권 귀속과 단일 마운트

Slot에 들어간 요소는 ownership이 귀속되며 다른 곳에 마운트할 수 없게 된다. isMounted를 관리해서, 한 인스턴스에 대한 다중 마운팅이 라이브러리 차원에서 절대 일어나지 않도록 강제하는 게 v1 대비 핵심 디자인 변화. v1의 mount()는 별다른 강제를 안 했지만(reference/quad-v1-architecture.md의 mount.lua 분석 참고 — 실제로는 부모/자식 부기까지 했지만 다중 마운트 방지는 없었음), v2는 mount 함수 자체가 이 강제를 담당.

Fusion의 Children SpecialKey는 이걸 "특정 SpecialKey 하나의 내부 부기"로만 구현했고(재사용 가능한 1급 프리미티브가 아님), Vide는 아예 이 개념이 없어서 같은 target에 두 번 mount()하면 조용히 두 개의 독립 루트가 생김 — 둘 다 반면교사.

여럿 존재 가능, 부모가 실제 데이터 테이블만 다루면 됨

Slot은 하나의 instance 안에 여럿 존재할 수 있다. 전부 하나의 children으로 들어가지만, 실제 렌더된 instance에서 GetChildren()을 직접 하지 않고도 부모가 생성한 "실제 slot 데이터 테이블"만 다루면 되게 해서 추상화 수준을 낮은 직접 바인딩에서 한 단계 떼어냄(간접화를 통한 추상화).

마운트된 Slot의 재마운트는 즉시 throw (확정)

사용자 확인 완료: 이미 사용된(마운트된) slot을 재마운트하려 하면 즉시 error()로 중단 — warn+no-op 아님. 개발 중 바로 잡아낼 수 있게 강하게 실패하는 쪽 선택. 마운트되는 순간 slot의 실제 대상은 고정된다 — 따라서 글로벌 스코프에서 slot을 쓰는 건 그다지 좋지 않을 수 있음(재사용/재마운트가 막히므로).

클래스가 슬롯을 받는 방법

"네이밍된 슬롯"이 필요한가에 대한 사용자 자문: 그냥 슬롯 바인드 테이블을 값으로 넘기면 되는 것 아닌가 — 결국 array처럼 구현된 Store라고 생각하는 게 편하다는 방향. 기울어진 결론: 별도 "Named Slot" 개념 없이, store나 파라미터로 넘기고 그게 그냥 ref처럼 바인드되는 모양.

확정(2026-08-04, 로드맵 인수인계 라운드): 위 방향 그대로 확정 — 별도 "Named Slot" 개념 없음, 슬롯 바인드 테이블을 store나 파라미터로 그냥 넘기면 ref처럼 바인드됨 — 사용자 확정("A. 맞음. 리프노드에선 그렇게 마운트됨"). 단, 이 확정은 "리프 레벨에서 슬롯 하나가 마운트되는 방식"에 한정 — 여러 Slot이 형제로 섞이는 경우의 순서 보장 문제는 별도로 열려있음, 바로 아래 참고.

여러 Slot이 섞일 때 순서 보장 — 열린 질문 (2026-08-04 신규)

Frame { Slot1, 일반자식, Slot2 }처럼 Slot과 Slot 사이에 다른 요소가 끼거나 Slot이 여럿 형제로 존재할 때, 최종 자식 순서가 저작 순서(위쪽 Slot의 요소가 항상 아래쪽 Slot의 요소보다 앞)를 안정적으로 지키는지는 아직 설계 안 됨 — 사용자 확정("순서도 따름, 만약 웹이라면 순서에 맞춰서 마운트 되어야 하고, 아래쪽 slot은 위쪽 slot의 요소보다 무조건 아래 있어야함... 이게 가능하냐는 생각해봐야할 이야기임"). Roblox 자체는 z-순서를 주로 LayoutOrder/ZIndex로 푸는 편이라 당장 급하게 막히는 지점은 아니지만, architecture.md가 명시한 "quad-base는 다른 렌더 백엔드(GTK, 웹 DOM 등)에서도 재사용 가능해야 한다"는 전제와 직결되는 문제 — DOM류 백엔드는 형제 순서 자체가 렌더 순서라 이 보장이 없으면 못 씀. 후순위 열린 질문으로 유지, Slot 코어 로직 구현 시점에 다시 볼 것 — M0/M1 단계를 막지는 않음(Roblox 단일 백엔드로는 당장 문제 없음).

Slot과 Store 바인드의 관계 (retract 순서)

Slot이 store 바인드로 들어오는 경우, pluggable 처리기에 retract(구 cleanup, base/lifecycle-pattern.md 참고) 핸들러가 필요함 — 한번 넘어간 slot 요소가 나중에 retract되면 삭제되는지, 아니면 "부모의 소유이니 부모가 처리"해야 하는지 검토 필요. 기울어진 결론(잠정안, 이후 정정됨): 부모가 정리 정도만 미리 수행하고 다시 process하면 되므로, 부모에게 위임(자식 slot 요소 자체가 스스로 정리를 실행하는 게 아니라).

정정(2026-08-04 검증 라운드): 위 "부모 위임" 잠정안은 이후 폐기 쪽으로 정정됨 — 아래 "확정" 절과 .claude/question.md("Slot의 retract 동작이 '부모 위임' 잠정안에서 '폐기(옮기지 않음)'로 확정") 참고. 이 문단은 검토 과정의 히스토리로만 남겨둠, 현재 유효한 동작 아님.

이건 base/bind-system-plan.md의 "Store 바인드는 재실행 래핑" 확정 모델과 맞물림 — slot이 store 값으로 오면, store 바인드 핸들러가 이전 slot 상태를 retract하고 새 slot 상태로 다시 process하는 사이클을 돈다는 뜻. Slot 핸들러 자신이 감시 중인 값(배열/스토어)이 바뀔 때 child를 갱신하는 추적(구독)도 base/bind-system-plan.md가 말하는 "process 함수가 다른 값 변경을 추적해도 됨" 범위에 속하고, retract 시점엔 그 추적만 풀면 됨 — Destroy 시점엔 retract가 호출되지 않는다는 원칙(base/lifecycle-pattern.md)도 동일하게 적용.

확정(2026-08-04 검증 라운드): retract되는 slot은 옮겨지지 않고 그냥 폐기된다. Slot은 바인딩되는 순간 그 안의 요소를 전부 own해버리는 데이터형 — 새 slot 상태로 교체될 때 이전 slot의 내용을 다른 곳으로 옮기는 경로는 없음, 그냥 버림. React의 portal(<></>)류로 나중에 옮길 수 있게 하는 것도 검토됐으나 이번 마일스톤에서는 오버엔지니어링으로 판단, 하지 않음 — 필요성이 명확해지면 그때 별도로 다시 논의.

자식으로 넘기는 클래스 스토어

자식에게 내려주는 클래스 스토어는 부모 쪽에서 미리 만들어서 내려보내는 게 편할 것 같다는 방향 — store<<ChildClass.Props>> 형태로 구성된 스토어를 만들면 됨(타입 표기는 러프한 스케치, 실제 문법은 tbox의 명시적 제네릭 적용 패턴 f<<T>>(...).claude/initreq/tbox/CLAUDE.md:40-41 — 참고해서 확정할 것).

열린 질문 (.claude/question.md에도 취합)

  • 재마운트 에러 처리(throw), retract 시 폐기(옮기지 않음) 둘 다 확정. 남은 건 실제 구현 단계에서 이 "폐기" 동작이 실사용에서 불편하지 않은지 재검증하는 정도 — 설계 방향 자체는 더 이상 열려있지 않음.
  • "클래스가 슬롯을 받는 방법"(Named Slot 없음)도 확정됨(위 "클래스가 슬롯을 받는 방법" 절 참고).
  • 여러 Slot이 형제로 섞일 때 순서 보장은 아직 열려있음(위 "여러 Slot이 섞일 때 순서 보장" 절 참고) — Roblox 단일 백엔드로는 급하지 않음, Slot 코어 로직 구현 시점에 재검토.