- use-after-destroy 검증 안전망: rbvm 영역/quad-debug 스코프 밖으로 최종
기각, Ref 사용 관례(useRef급 스코프) 명문화
- :With 동적 의존성: State immutable 가정과 모순되어 의도적 비지원 확정
- Operator 콤비네이터 카탈로그: 서브 에이전트 외부 리서치로 포함 범위/
네임스페이스 이름 근거 보강(Clamp/Min/Max 추가 후보, 비트/비교/Sub/Div
드랍 후보, Debounce/Throttle 별도 질문으로 분리)
- State 용어 정리 최종 확정(현재 이름 유지), Pipe 기각 근거·Compute vs
Computed 네이밍 근거 문서화, :With와 Tag/Modifier clone 체이닝 혼동
방지 경고 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
사용자가 우선순위 동률/매치실패(1-3), provider 미주입(1-4), store.key
type function 타이핑(1-10), Modifier __index+table.clone 트릭(1-11)에
대해 구체적 결정을 제시 - 전부 base/ 문서에 반영해 우선순위1 11개 전원
해소. 핸드오버 점검 중 1-10/1-11이 설계 레벨로만 확인됐고 실제 Luau
스파이크 파일이 없었던 갭을 발견해 16/17 신규 추가, ROADMAP.md M2/M3/M7
체크리스트 누락분도 보강.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
- 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
직전 커밋이 "Slot의 T(Instance|Slot)가 우연히 테이블이라 T|{T}가
모호하다"고 적었는데, 이는 quad-roblox라는 특정 백엔드의 구체적 T에
기댄 특수 사례일 뿐 근본 원인이 아님 — 정확한 이유는 Slot<T>가 base
레벨에선 T가 뭔지 전혀 모르는 제네릭(다른 백엔드면 테이블/userdata/
그 밖에 뭐든 가능)이라, 바깥 {}가 단일 T인지 {T} 배열인지 원천적으로
판별 불가능하다는 것.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tag:Added가 string|{string}로 간 진짜 이유를 Lua table.unpack의 tail
위치 제약(뒤에 다른 인자/unpack이 오면 첫 값 하나로 잘림)으로 정확히
보강. 같은 논거가 Slot:Splice의 newElements에도 적용되는지 검토했으나
기각 — 사용 패턴이 소수 고정이고 동적이면 Slot-in-Slot으로 흡수 가능,
게다가 Slot의 T 자체가 테이블이라 T|{T}가 오히려 모호해짐 — vararg 유지로 확정.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
동적으로 조립한 이름 목록을 vararg에 넘기려면 table.unpack이 필요해
번거롭다는 지적 — string 단일 이름 또는 {string} 배열을 받아 내부에서
flatten하는 쪽으로 같은 세션 내 재수렴. Tag(...) 생성자는 정적 호출
자리라 vararg 유지.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tag:Added/:Removed가 문서상 단일 name만 받던 불일치를 vararg로 정정
(self-return 최적화는 멤버십을 매번 먼저 읽어야 해서 기각). Slot:Splice
CRUD 신설 — 구간 제거+삽입을 shift/recompute 1회로 묶는 순수 최적화.
Slot-in-Slot relate 범위/Animate 반환타입/Slot retract 파괴는 기존
문서와 일치 확인만.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
User cited https://luau.org/compatibility/ (Lua 5.2 section, "Ephemeron
tables") -- Luau explicitly did not adopt this due to complexity. Upgrades
the previous session's defensive "unverified, avoid just in case" framing
to a confirmed fact: two separate Relates strongly cross-referencing each
other's keys is a real, unrecoverable leak in Luau, not just a theoretical
risk. Adds a general rule to relate-plan.md so future Relate designs don't
have to rediscover this from scratch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two separate Relates strongly holding each other's key as their value
(inst->slot in one, slot->inst in the other) is a mutual-reachability
cycle that ordinary weak-key GC can't resolve on its own -- the case
Lua 5.2 ephemerons exist for. Weaken both to pure lookups and make
bindLifetime/unbindLifetime (already used for Slot's own observers)
the single GC anchor for the slot object itself, which attachSlot/
destroySlotTree were missing.
Audited every other Relate() in base/ for the same shape (an object
other than inst used as the outer key) -- slotOwner was the only one;
everything else either has no back-reference to inst or is the safe
single-table self-reference shape bindLifetime already relies on.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Position-keyed comparison only catches "did this exact slot spot change,"
not "is this same Slot object already mounted somewhere else" -- which is
exactly the double-mount invariant Slot already promises for its elements.
A Relate<slot, inst> enforces it directly: same owner -> spurious re-emit,
ignore; different owner -> error; no owner -> bind.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrects a foundational error: "handler type unchanged -> retract skipped,
process diffs" was never actually true. StoreBind unconditionally calls
Dispatch.retractUnder before every re-dispatch (already stated in the
original dispatch model doc) -- Tag's old assert(v==nil) was taken at
face value and the general rule was wrongly back-derived from it.
Tag, Ref, Slot, and Attribute (the last three designed earlier this same
session) all inherited the flawed premise. Fixes across all four:
- Tag: rewritten around kTagMap/tagNameMap reference counting so
AddTag is entirely process's job, RemoveTag entirely retract's,
using a Contains hint to skip engine calls that would be immediately
redone. Also fixes the case where two independent Tag(...) values at
different positions share a name (union semantics, like className).
- Ref/Slot: drop the incorrect assert(v==nil), split into "retract
unbinds/destroys the old, process binds/mounts the new" with an
identity check on both sides to avoid spurious re-fire.
- Attribute: AttributeKeyHandler.retract gated on v==nil so ordinary
value updates don't flicker the attribute to nil; the group's
"unchanged name" delegation now also retracts before reprocessing,
since skipping it was silently stacking chain entries every cycle.
Reversal preserved in archive/retract-always-fires-reversed.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Group Attribute(...) delegating through the public memoized AttributeKey(name)
cache meant a direct [AttributeKey "name"]=v write and a group field (or two
independent groups) targeting the same name silently converged on the same
dispatch slot -- last write wins, no error. Fix: groups use rawNew(name) to
mint a private per-name key cached on their own (inst,index) relation instead
of the shared cache, so AttributeKeyHandler can detect a conflicting claimant
by simple object-identity comparison -- no separate ownership registry needed.
Persisting names reuse the same cached key across group value swaps, so the
existing diff (nil only what left, leave the rest alone) keeps working.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same gap as Ref: slotA -> slotB via State<Slot> matches the same
SlotHandler both times, so Dispatch skips retract and process must
diff. Slot's confirmed no-portal/discard-only policy makes an identity
short-circuit a correctness requirement, not just an optimization --
without it, any spurious same-value re-emit would tear down and
rebuild the entire mounted subtree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
State<Ref> rebinds (refA -> refB) go through process's own diff (same
handler type matches both), not retract -- exactly the TagHandler
precedent. retract only fires when the slot stops being a Ref at all.
Both paths converge on old:Set(nil). Non-nilable Ref<T> remains a valid
"read a settled value" use case; callers opt into Ref<T?> explicitly for
Store/Modifier slots. Unbinding is independent of Instance Destroy --
that's Effect's job, not Ref's.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PreRef never enters the normal retract dispatch chain (consumed via None
in the pre-pass), so "cancel" was never structurally possible. The real
hazard is reuse across constructions (stale .Value silently firing
callbacks) — guard it with an explicit error on re-fire instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
state:Compute(fn)'s fn receives lazy State handles, not raw values, per
the confirmed self/with contract. Found and fixed the same class of bug
the user caught in the Animate example in three more spots (slot-plan.md
x2, tag-plan.md). Added a warning note near the contract since this is
an easy mistake to repeat.
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.
Animate(info) resolves T|State<T> option fields into a plain Tween{...},
matching :Compute's self-as-lazy-handle contract directly. Also audited
.claude/base for and/or ternary idioms per Luau if-then-else guidance;
fixed a real falsy-value leak in Dispatch.retractUnder.
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>
CLAUDE.md had grown to 3196 lines of accumulated session logs, causing
context bloat. Full session narratives (including trial-and-error and
later-corrected reasoning — quadnomicon devlog raw material) now live
as 39 individual files under .claude/session/. CLAUDE.md keeps only a
short "지금 할 일" (re-synced against question.md/pre-implementation-audit.md,
stale detail dropped) and a compact per-session summary+link table.
No design decisions changed; base/research/question.md were already
in sync with every session (verified against README.md/question.md
before archiving), so no unreflected content needed migrating first.
Slot:Add now accepts State<T>/Source<T> elements directly, arbitrarily
nested. Implemented as pure sugar (isState(element) -> internally
Slot():Single(element)) rather than a new position-keyed StoreBind
mechanism, since the latter would reintroduce array-part None, Length
special-casing, and Move/Swap index-subscription sync bugs that :List's
key-based design specifically avoids. Slot:Single's updateFn is now
optional (defaults to identity) to support this sugar. Also fixes
:List's reconcile to skip the compact index by a nested-Slot result's
.Length instead of a flat +1, so multi-root list items don't produce
overlapping LayoutOrder ranges for subsequent siblings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ymPLYAQmEYnTrnrdAwxpU
Slot:Single confirmed as pure :List sugar (resolves the original
State<Frame?> offset-access motivation directly). Slot-in-Slot nesting
confirmed for component-composition uniformity: Dispatch.setLength/
setOffsetSource reused recursively keyed by the Slot object itself
(no new primitive), Slot.Length becomes a contribution-sum, teardown
is a flat destroySlotTree + explicit unbindLifetime instead of
recursive Clear(). Slot(initial?) constructor revived as pure :Add
sugar, with a new _crudUsed <-> _listed mutual-exclusion guard.
Also fixes a real off-by-one bug in the existing (pre-nesting)
Length/Offset recompute — offset was accumulating inclusive of its
own position instead of exclusive. A reentrancy guard was considered
and rejected: each Slot owns an independent bookkeeping table, so
nesting alone never re-enters the same one; genuine reentrancy is
now named UB under the same Source-to-State unidirectional-flow
principle already governing Source/State.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ymPLYAQmEYnTrnrdAwxpU
Confirms Slot's documentation tone as "동적 렌더링을 가능하게 하는 도구"
rather than a static children-array description, extends the framing to
the still-unstarted :Single backlog item, and fixes an adjacent stale
marker (sibling Slot ordering was already resolved via Length/Offset).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Slot no longer auto-binds LayoutOrder onto mounted elements (was
magic/layering violation) - index (raw, compacted position) and
offset (Slot.Offset, now a public field like Length) are passed to
updateFn as plain values; actual property binding is entirely
updateFn's responsibility via its own userdata-managed Source
- updateFn(item, index, offset, prev, userdata) param order now
mirrors return order (prev, userdata) instead of the reversed one
- updateFn's three branches (discard/redraw/update-only) documented
explicitly - only updateFn knows which applies, so deciding Set
timing outside it wastes writes on soon-to-be-discarded Sources
- fix reconcile bug: rawAdd/rawMove was using the raw data-array loop
index instead of a compacted mounted-position counter, which could
error once filtering was in play; introduced candidateIndex
- add duplicate-key guard (seen[key] check, ~free) and explicit
key/index terminology disambiguation (raw data index vs key vs
updateFn's compacted index all get confused otherwise)
- sync ROADMAP.md M6 and .claude/README.md summaries to match
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expose :Compute(fn, ...)'s trailing deps to fn as lazy State-handle
positional args (removes the closure-capture duplication/drift risk in
curried fn factories). Confirms self's lazy-handle sugar generalizes
here because trailing args are local to one call, unlike :With chains.
previous must sit right after self, before the deps pack
(fn(self, previous?, ...deps)) — not after it as first proposed:
Luau's "..." must trail the parameter list, so a fixed arg after a
generic type pack is very likely unrepresentable. Adds luau-test 15
with a negative control (old broken ordering) and positive control
(corrected ordering) to verify, plus the remaining open question
(whether heterogeneous dep types survive one generic pack).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BBBW9GakG8J3CFumTSJ8bw
Compute 노드는 결과를 담을 새 State 노드를 어차피 만들어야 하므로
추가 의존성 구독을 그 노드에 얹는 건 공짜 sugar로 확정. Effect/Observer는
자기 자신이 State 노드가 아니라 다중 의존성 병합에 새 노드가 실제로
필요해서 동일 sugar를 의도적으로 제외, :With(...)를 코드에 그대로
노출하도록 유지.
이벤트 문자열 키 패턴이 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>
Slot의 Length/Offset 카운팅이 Dispatch.setLength/setOffsetSource 호출에
의존하는데, 이 둘을 우회하는 경로(외부에서 직접 .Parent = inst로 자식을
끼워 넣는 것)가 UB라는 게 문서 어디에도 명시돼 있지 않았던 갭을 보강.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
base/ 확정 사항 중 아직 실제 Luau로 부딪혀본 적 없는 것(M0 스파이크
대상)을 사용자가 luau/luau-analyze/luau-lsp/Roblox Studio로 직접
돌려볼 수 있는 독립 실행 스크립트 14개 + README 색인으로 정리. 커밋
f198fd9의 정정사항(Ref 콜백/대기자 배열 소진을 None에서 nil로 되돌린
것 등)을 반영해 02번을 재작성했고, 타입 관련 실측이 필요한 항목
(Attribute 제네릭 DI 키 narrowing, Ref/PreRef 구조적 서브타입, Source/
Ref nilable-default 오버로드)을 새로 찾아 12~14번으로 추가함. 처음엔
git 자동 제외 폴더(luau-ignoreme/)에 만들었으나 커밋해서 레포에
남기기로 해 .claude/luau-test/로 이동, .claude/README.md에 색인 추가.
CLAUDE.md에 세션 요약 반영 — 아직 실행 결과는 미확인.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.claude/base/ 전체를 배치별로 리스팅해 사용자 확인을 받는 중간검토
세션 — Ref 콜백/대기자 배열의 None 소진이 무한 성장 버그였던 것을
nil로 되돌리고, isRef/isPreRef를 isState/isSource와 같은 상위-하위
합성으로 재정정, Slot CRUD 식별 기준을 element 레퍼런스에서 인덱스
기준으로 전환(ExtractAll/Get/IndexOf 신설), 컴포넌트 리프 바인딩에서
Source 직접 사용이 정상 경로라는 정정, Dispatch 직접 호출 UB 명시,
Tag retract 전제 명시, Attribute 타입 파라미터화 확정, EffectHandle
내부 Observer cascade/Subscribe GC 예외 경고 등을 반영. CLAUDE.md에
세션 요약, stale해진 research/documentation-content-map.md 일부 항목도
동기화.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
Dispatch.setLength는 이미 마운트 시점에 bindLifetime을 걸었지만, Slot:List의
data:Observer(fn) 구독은 :List() 호출 시점(inst를 모르는 시점)에 즉시
생성되어 Destroy 후에도 재실행/관측을 멈출 방법이 없었음. :List()는 이제
설정만 저장하고, 실제 구독+최초 reconcile은 Slot 마운트 시점에 activateList로
수행 — 마운트 이후 :List() 호출 시 self._mounted로 즉시 활성화.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
recompute 스케치가 offset==None을 truthy로 오통과시켜 None:Get()을
부르는 실제 버그를 발견해 수정. sourceList가 nil 대신 None을 쓰는
근거, Slot 자신이 아니라 그 위치를 매치한 Handler가 setLength를
호출한다는 책임 소재도 명시.
여러 Slot이 형제로 섞일 때 LayoutOrder 순서 보장 메커니즘을
Dispatch.setLength/setOffsetSource 누적합+리액티브 바인딩으로 확정하고
base/slot-plan.md의 관련 열린 질문을 해소. 이 과정에서 발견된 라이프사이클
게이트의 실제 모양(진짜 독립 경로는 :Subscribe()/bindLifetime 둘뿐이고
leaf 부착은 bindLifetime 호출 그 자체라는 점, canBound의 내부 플래그가
canExecute가 보는 .Subscribed와 동일 필드라는 점)을 반영해 이중 바인딩
금지 규칙과 기존 StoreBind 예제를 정정.
- Slot CRUD를 Add/Remove/Extract/Clear/Move/Swap 6종으로 확정, get/set 드롭
- "원시 연산 최소화" 원칙 뒤집고 Move/Swap 추가 — Extract+Add 기반 리오더가
Parent 조작 두 번(detach+reattach)이라 무겁고, :List 없이 수동 구성한
Slot엔 리오더 수단 자체가 없었음. Swap은 element 아닌 index 기준(element면
위치 조회에 2n 들어 O(1) 약속이 깨짐)
- isMounted 이중 추적 분리(Slot 컨테이너 self._mounted vs 개별 element
전역 weak-set), pre-implementation-audit.md 1-7/1-8 해소
- 요소 타입 제약 신설 — nil/None 둘 다 raw 요소로 금지, 핸들러 계층 값
(Ref/PreRef/Observer/Effect/Modifier)은 self-ref 컨텍스트가 없어 의미
불성립이라 즉시 error(Modifier 필드와 같은 판별 메커니즘 재사용).
Slot<T>() 제네릭화
- Slot:List(data, updateFn, keyFn?) 신설 — 키 기반 동적 컬렉션 재조정,
research/additional-primitives-plan.md에서 승격·통합. keyFn 생략 시
index를 key로 사용(80% 케이스 커버, 캐스케이드 갱신 트레이드오프 명시)
- updateFn<UD>(item, index, userdata, prev)이 매 reconcile 사이클마다
호출 — filter/toggle이 Visible 토글이 아니라 실제 파괴/재생성이 되도록
재설계(200+ 항목에서 lazy하지 않은 문제 회피), prev 재사용이 저비용 경로
- :List가 Source를 더 이상 안 만들고 userdata로 그 권한을 updateFn에 위임 —
result/userdata 반환값 커플링 제거, 정리 루프는 mounted가 아니라 직전
keyIndex 전체를 순회해야 함(userdata만 살아남는 케이스 커버)
- userdata는 GC-native 값만 허용, 명시적 cleanup 필요한 값은 UB로 명문화 —
item=nil 정리 훅 추가안은 부모 Destroy 경로에서 안 불려 절반만 동작하므로
기각(retract가 Destroy 시 안 불리는 것과 같은 이유)
- question.md/ROADMAP.md/README.md 동기화, slot-plan.md 자체 정합성 재감사
(stale 백로그 섹션 제거 등)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
이미 해소된 결정이 미해결로 표시되거나 문서 간 모순되던 항목 7개 파일
수정, 뒤집힌/무효화된 설계 서술이 정정 표시만 붙은 채 본문에 남아있던
곳을 기존 archive 컨벤션대로 이전(quad2-try 리서치, Observer cleanup
계약, keyed collection state method, debug channel ReplicatedStorage).
CLAUDE.md에 세션 로그 반영.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
사용자가 "이전 핸들러 추적 책임 소재"가 이미 Dispatch 체인/retractUnder로
해소됐던 걸 지적 — question.md 164행이 이걸 아직 열린 것처럼 동률/매치실패
처리와 한 bullet에 묶어놓고 있었음. 겸사겸사 previous 인자(이번 세션에 해소),
UICorner 매칭 기준(ui-shorthand-plan.md에 진작 확정됐으나 audit 2-11에
표시만 누락)도 같이 동기화. 실제로 남은 건 Slot CRUD 의미론(1-7)과
우선순위 스캔 동률/매치실패 처리(1-3) 두 개뿐.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
"프로바이더" 개념을 Handler로 최종 확정하고 Processor/Provider/Plug를 기각한
근거를 보강. Ref/PreRef/Peek/isState/None/NoneHandler는 현재 이름 그대로
유지로 확정해 question.md 3순위 재검토 목록에서 정리. DI→D 축약안과
canExecute→isAlive 대체안은 근거는 쌓였지만 아직 미확정으로 남김.