Commit graph

236 commits

Author SHA1 Message Date
95fdfc4b12
decide(slot): break the kSlotMap/slotOwner mutual strong-reference cycle
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>
2026-08-12 16:37:40 +09:00
2b7e90cd92
decide(slot): track slot->inst ownership directly instead of position diffing
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>
2026-08-12 16:20:29 +09:00
f20922ce39
decide(bind-system): retract fires on every re-dispatch, not just type changes
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>
2026-08-12 16:13:25 +09:00
6b7af54e0d
decide(attribute): per-name ownership via private rawNew keys, not a new registry
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>
2026-08-12 15:23:24 +09:00
27adad9c1a
decide(slot): store-bound Slot rebind uses Relate identity check, not retract
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>
2026-08-12 13:49:58 +09:00
a985874ca9
decide(bind-system): Ref retract mirrors TagHandler, non-nilable T stays valid
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>
2026-08-12 13:44:31 +09:00
cdbbdba4fb
decide(bind-system): PreRef is single-use, no cancel concept, reuse errors
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>
2026-08-12 13:26:46 +09:00
e6880e518c
decide(tween): confirm natural-completion bookkeeping is left as-is, promote to base/
Completed 시 per-instance 북키핑 정리는 불필요 — 자연완료는 유저가 원한
목표값에 도달한 상태라 남은 참조가 부작용 없고, Value가 lerp 가능한
프리미티브라 메모리 문제도 없어 별도 정리 장치는 오버엔지니어링. 이걸로
tween-plan.md에 남은 열린 질문이 없어져 research/에서 base/로 승격,
라이브 크로스레퍼런스 전부 갱신.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 12:19:49 +09:00
3e37d9bc36
decide(operator): add Concat/Sorted/Filtered collection combinator candidates
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 12:13:38 +09:00
9909aca227
decide(operator): add Operator sugar plan, unify combinators on :Apply
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>
2026-08-12 11:54:09 +09:00
34ded8b953
fix(docs): missing :Get() on :Compute callback args across base docs
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.
2026-08-12 11:22:26 +09:00
a1f8601cc7
decide(tween): add Animate CanAnimate field, document Luau syntax facts
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.
2026-08-12 11:17:49 +09:00
8fdb9f1bb2
decide(tween): finalize Animate combinator, fix and/or falsy-value bug
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.
2026-08-12 11:14:08 +09:00
eac66d0173
decide(tween): finalize option shape, override policy, relation-slot value
Info-first with TweenInfo.new() default fallback, plain-only option
fields, Cancel/Finish override collapse, initValue handed off to user.
2026-08-12 11:02:13 +09:00
6216f12f37
decide(attribute): group Attribute(...) primitive, AttributeKey rename, weak-cache identity
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>
2026-08-11 18:22:54 +09:00
1f56c75978
chore(docs): split CLAUDE.md session log into .claude/session/, keep 2-4 line summaries
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.
2026-08-11 14:40:43 +09:00
6ea3afa76a
decide(slot): reactive raw elements via Slot:Single sugar, :List index skip fix
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
2026-08-11 14:25:19 +09:00
cebef6d973
decide(slot): Slot:Single + Slot-in-Slot nesting, fix recompute off-by-one
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
2026-08-11 13:55:31 +09:00
c02f52578f
decide(slot): frame Slot docs as a dynamic-rendering tool
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>
2026-08-11 12:15:32 +09:00
895a17c212
decide(slot): Slot:List index/offset ownership moves to updateFn
- 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>
2026-08-11 12:06:38 +09:00
b668109eaf
decide(bind-system): :Compute trailing deps as fn positional args, fix previous ordering
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
2026-08-11 10:52:20 +09:00
9370acd394
decide(bind-system): Compute(fn, ...) trailing-args sugar 확정
Compute 노드는 결과를 담을 새 State 노드를 어차피 만들어야 하므로
추가 의존성 구독을 그 노드에 얹는 건 공짜 sugar로 확정. Effect/Observer는
자기 자신이 State 노드가 아니라 다중 의존성 병합에 새 노드가 실제로
필요해서 동일 sugar를 의도적으로 제외, :With(...)를 코드에 그대로
노출하도록 유지.
2026-08-11 10:29:35 +09:00
32601487ae
chore: add empty folders 2026-08-10 01:22:53 +09:00
113b160b81
Merge remote-tracking branch 'origin/quad2' 2026-08-10 01:17:08 +09:00
fa2c7b900a
Cleanup repo 2026-08-10 01:13:19 +09:00
qwreey
4824bab145
Merge pull request #7 from qwreey/dev3-linker
Dev3 linker
2026-08-10 01:10:33 +09:00
2be4fffeca
decide(bind-system): OnChange 특수 키 신설 - GetPropertyChangedSignal 바인딩, 제네릭 없이 확정
이벤트 문자열 키 패턴이 GetPropertyChangedSignal엔 안 통해서(프로퍼티 이름이
값 세팅 키와 겹침) 별도 OnChange(name) DI 키를 신설. Attribute와 달리 제네릭
타입 파라미터 없이 콜백 타입은 인라인 명시 - 이벤트 바인딩과 같은 급의 타입
안전성 트레이드오프. 전부 quad-roblox 소속, State<function>은 기존 이벤트
store-bind 메커니즘 재사용. 프로퍼티별 정적 코드 생성 안은 규모 폭발로 기각.
2026-08-10 01:08:02 +09:00
7466e9216b
decide(tween): 값-레벨 Tween<T> 래퍼로 재설계, pre-implementation-audit 1-1 해소
독립 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>
2026-08-10 00:53:26 +09:00
fc43e1f267
Merge branch 'worktree-slot-parent-ub-doc'
동적 자식 추가/제거는 Slot/state<Frame>만 정당, 그 외는 UB로 명문화하는
문서 갭 보강 작업을 main에 통합.

# Conflicts:
#	CLAUDE.md
2026-08-10 00:14:49 +09:00
577277823a
docs(base): 동적 자식 추가/제거는 Slot/state<Frame>만 정당, 그 외는 UB로 명문화
Slot의 Length/Offset 카운팅이 Dispatch.setLength/setOffsetSource 호출에
의존하는데, 이 둘을 우회하는 경로(외부에서 직접 .Parent = inst로 자식을
끼워 넣는 것)가 UB라는 게 문서 어디에도 명시돼 있지 않았던 갭을 보강.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 00:11:12 +09:00
85e467152c
decide(base): Slot:Add가 삽입 인덱스 반환, 범위 밖 index는 clamp 대신 error
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 00:09:29 +09:00
df4a77b02d
docs(luau-test): M0 사전검증 스파이크 신설, luau-ignoreme→.claude/luau-test 이동
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>
2026-08-09 23:55:22 +09:00
f198fd9c6b
fix(base): 중간검토(질문 모드)에서 발견된 설계 결함 다수 수정
.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
2026-08-09 23:37:46 +09:00
5836c2d12a
decide(base): Slot:List의 data:Observer 구독을 마운트 시점 lazy bindLifetime으로 통일
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
2026-08-09 21:42:24 +09:00
97c074ed93
fix(base): Length/Offset 문서 최종 점검 — recompute의 None 처리 버그, setLength 호출 책임 소재 보강
recompute 스케치가 offset==None을 truthy로 오통과시켜 None:Get()을
부르는 실제 버그를 발견해 수정. sourceList가 nil 대신 None을 쓰는
근거, Slot 자신이 아니라 그 위치를 매치한 Handler가 setLength를
호출한다는 책임 소재도 명시.
2026-08-09 21:03:23 +09:00
b9cfe8e99d
decide(base): Slot 형제 순서 보장(Length/Offset), bindLifetime/unbindLifetime과 canBound 이중 바인딩 게이트 통합
여러 Slot이 형제로 섞일 때 LayoutOrder 순서 보장 메커니즘을
Dispatch.setLength/setOffsetSource 누적합+리액티브 바인딩으로 확정하고
base/slot-plan.md의 관련 열린 질문을 해소. 이 과정에서 발견된 라이프사이클
게이트의 실제 모양(진짜 독립 경로는 :Subscribe()/bindLifetime 둘뿐이고
leaf 부착은 bindLifetime 호출 그 자체라는 점, canBound의 내부 플래그가
canExecute가 보는 .Subscribed와 동일 필드라는 점)을 반영해 이중 바인딩
금지 규칙과 기존 StoreBind 예제를 정정.
2026-08-09 20:58:56 +09:00
baa004ad42
decide(base): Slot CRUD(Add/Remove/Extract/Clear/Move/Swap)·요소 타입 제약 확정, Slot:List 신설
- 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
2026-08-09 19:21:22 +09:00
911ab559ea
fix(base): 코퍼스 전체 stale 마커/모순 감사 및 무효화된 인라인 서사 archive 이전
이미 해소된 결정이 미해결로 표시되거나 문서 간 모순되던 항목 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
2026-08-09 16:36:59 +09:00
23c2a8ae46
fix(base): question.md/audit 문서의 stale 번들 항목 정리 — 이미 해소된 것과 진짜 열린 것 분리
사용자가 "이전 핸들러 추적 책임 소재"가 이미 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
2026-08-09 16:04:45 +09:00
8169b90a0e
decide(base): canBound 이름 확정, Compute previous 방어/스코핑 명확화, Modifier 핸들러값·State<Modifier> UB→error 전환
- Bound 플래그 → canBound(handle) 탑레벨 함수로 확정(canExecute와 같은 결)
- :Compute의 previous 인자 오버엔지니어링 의심 기각, 결과 노드 귀속으로 스코핑 명확화(self.Cache 안 씀 — 팬아웃 충돌 회피)
- Modifier 필드의 Ref/PreRef/Observer/Effect/Slot/Modifier 값: UB → isX predicate 기반 즉시 error
- State/Source가 Modifier를 값으로 담는 것(State<Modifier>)도 동일하게 error로 통일, Slot 등은 계속 허용
- Tween initValue/useTween 논의 신설(미확정)
- pre-implementation-audit.md 3-1/2-2/문서모순 절 해소 반영, question.md/ROADMAP.md 동기화

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYfAz3BsaTrmMM8hnzn6mj
2026-08-09 16:00:19 +09:00
4f3badf414
docs(base): 코퍼스 전체 정합성 감사 반영, 기각된 대안 archive 이관
병렬 서브에이전트 4개로 base/reference/research/archive 전체를 재감사해
발견한 14건 수정 — canExecute(inst,value) 시그니처 통일, Dispatch.process가
Dispatch 체인/retractUnder와 모순되던 서술 정정, "프로바이더"→Handler 잔재
정리, Overridden 오타, :Peek 반환 타입에 None 누락, Peek/isState 미정
표기 해소 누락, slot CRUD 의미론 갭 미표기, Relate 개명 반영 누락,
pre-implementation-audit.md 자기모순(이미 해소된 1-2 재언급, 옛 UI
숏핸드 이름), documentation-content-map.md 신구 이름 자기모순 및
TagService/CollectionService 재발, README.md archive 색인 누락 행,
agent-mistake.md 카테고리 태그 누락.

modifier-plan.md 9-1번 절에 인라인으로 남아있던 기각된 Apply-mutable
대안 두 개의 전체 경위를 archive/modifier-apply-mutable-rejected.md로
이관하고 본문은 결론+포인터로 압축 — quadnomicon 개발로그 소재 확보,
컨텍스트 비대화 방지.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 11:38:29 +09:00
959a519533
decide(base): Handler/None/NoneHandler/Ref/PreRef/Peek/isState 이름 확정, DI→D·canExecute→isAlive는 계속 미정
"프로바이더" 개념을 Handler로 최종 확정하고 Processor/Provider/Plug를 기각한
근거를 보강. Ref/PreRef/Peek/isState/None/NoneHandler는 현재 이름 그대로
유지로 확정해 question.md 3순위 재검토 목록에서 정리. DI→D 축약안과
canExecute→isAlive 대체안은 근거는 쌓였지만 아직 미확정으로 남김.
2026-08-08 03:57:38 +09:00
1a612ecfa0
decide(base): Modifier.Override를 Overridden으로 이름 확정
Add/Remove→Added/Removed, Merge→Merged와 같은 분사형 네이밍 컨벤션을
Override에도 적용하되, override가 불규칙동사임을 반영해 정확한 과거분사
Overridden을 채택(Overrided는 오기). question.md 용어 재검토 목록에서
제거하고 관련 base/research 문서 전반에 반영.
2026-08-08 03:37:32 +09:00
54a46aaf7e
decide(base): Tag를 array-part 값 객체로 재설계, Dispatch 체인+retractUnder로 retract 전파 확정
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로 꼬리부터 정리하는 방식으로 해결.
2026-08-08 03:23:44 +09:00
75cae39c7c
docs(base): module-lifecycle-plan.md의 stale 열린질문 절 정리
프로바이더 인터페이스 시그니처/네이밍 미정 항목이 Handler 계약 확정으로
이미 풀려 있었는데 반영이 안 되어 있던 것을 사용자가 발견, 동기화.
2026-08-08 01:52:18 +09:00
9ab63863a9
decide(base): Dispatch 탑레벨 싱글톤 확정, 네이밍 케이싱 컨벤션 신설, Handler 세 번째 카테고리 명문화
Ref/Observer/PreRef leaf Handler 위치를 quad-base로 확정하며 question.md
미결 항목 해소.
2026-08-08 01:15:06 +09:00
c865e99860
decide(base): Relate 프리미티브 신설, bindLifetime/canExecute 확정, retract 필수화
- 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 전체 동기화
2026-08-08 00:54:48 +09:00
33790df6ee
decide(base): CreatedRef 폐기, PreRef pre-pass 확정, None 소진 정정, nil-hole 방지 관용구 확정
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>
2026-08-07 20:29:36 +09:00
98bd46af09
docs: 코퍼스 전체 정합성 감사 반영, agent-mistake.md 신설
여러 세션에 걸쳐 쌓인 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>
2026-08-07 19:34:40 +09:00
d947abf17a
decide(base): None 센티널, Dispatch 네이밍, Brand 판별 메커니즘 확정
- Modifier 필드를 인라인 키/setter로 명시적으로 지우는 `None` 센티널
  확정 — merge는 안 바뀌고, 디스패치 쪽 NoneHandler가 Tween store-bind와
  같은 재귀 재디스패치로 처리(base 드라이버/개별 핸들러 시그니처 불변).
- Dispatch.getHandler/process/addHandler/drive로 오케스트레이터 이름
  공식화, isHandlable에 inst 추가, canExecute 시그니처를 (handle)->boolean
  으로 정정(zero-arg 클로저 폐기).
- isState를 Brand 공유 레지스트리로 일반화해 isObserver/isSource/isTag
  등 10종 판별자로 확장, isSource 별도 필요하다고 정정.
- Tag/Attribute retract 불필요함을 확인, 전용 문서(tag-plan.md/
  attribute-plan.md) 신설.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 18:10:58 +09:00