docs(base): Fallback/Traceback 승격 — research/에서 base/fallback-plan.md로
pcall 기반 Fallback과 xpcall+debug.traceback 기반 Traceback으로 분리, 정확한 제네릭 시그니처(OkComp/ErrComp/Args... 독립 제네릭)와 err: any를 확정. 패키지(quad-base)·이름(Fallback/Traceback) 확정으로 남은 열린 질문이 없어져 base/로 승격. 스파이크를 audit/fallback-xpcall-spike.luau로 옮기고 base/fallback-plan.md와 이름을 맞춰 내부 함수를 Traceback으로 정정, audit/fallback-xpcall-verification.md에 실측 결과 기록. README/ question.md/archive/question-resolved.md/lifecycle-hooks-plan.md의 상호 참조 동기화. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
f6117723eb
commit
2d9cc7b5f7
10 changed files with 274 additions and 186 deletions
|
|
@ -16,7 +16,7 @@
|
|||
| `archive/` | 완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). **[2026-08-06 확장]** 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용(제목 `[역전됨]` — 한 번 확정했다가 뒤집힌 것) — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 `base/`/`research/`에서 뺌) `quadnomicon` 소재로는 나중에 쓸 수 있음. **[2026-08-07 확장]** 후보였다가 채택 안 된 것(확정한 적 없이 검토 후 기각)도 같은 방식으로 보존, 제목은 구분을 위해 `[기각됨]` — `[역전됨]`과 의미가 다르므로 혼동하지 말 것. **[2026-08-07 세 번째 확장]** 설계 반전/기각과 별개로, 에이전트가 문서 작성 중 스스로 낸 개념 혼동을 정정한 이력은 `[에이전트 실수]` 태그로 `agent-mistake.md` 하나에 모음(CLAUDE.md 세션 로그 중복 방지) |
|
||||
| `feedback/` | 실사용 피드백을 정리한 긴 로그 — 지금은 비어있음(구현 시작 전) |
|
||||
| `luau-test/` | **[2026-08-09 신설]** `base/` 확정 사항 중 "추론만으로 확정하고 실제 Luau로 부딪혀본 적 없는 것"(M0 스파이크 대상)을 `luau`/`luau-analyze`/`luau-lsp`/Roblox Studio로 사용자가 직접 돌려볼 독립 실행 스크립트 모음. **[2026-08-13 여섯 번째 세션, 첫 실측]** `luau`/`luau-analyze` 바이너리가 생겨 처음으로 실제 실행 — **런타임 12개 전원 통과**, 타입 쪽에서 `:Compute(fn)` lazy 핸들 계약이 Luau 추론과 충돌하는 게 드러남(당시 `question.md` 0-Y). **[2026-08-13 열세 번째 세션]** 그 0-Y가 해소되며 `review-required/`가 **비었음** — 계약은 유지 확정, 남은 건 Luau 자체 한계라 `base/typing-limits.md`가 담당. **`STATUS.md`가 상태의 소스**(pass / 사람 결정 필요 / 스파이크 깨짐 / 미실행 분류 — 사람이 먼저 볼 것만 위에), `luau-test/README.md`는 각 파일의 검증 의도·배경, 실행 결과 상세는 `audit/luau-test-first-run-2026-08-13.md` |
|
||||
| `audit/` | **[2026-08-13 신설]** `luau-test/` 등 스파이크를 실제로 돌려본 뒤 "무엇이 확인됐고 무엇이 아직 안 됐는지"를 기록하는 곳 — 스크립트/계획 자체가 아니라 **실측 결과**만 다룸. base/luau-test와 달리 부분 확인(일부만 통과)도 있는 그대로 기록, 완전히 해소되면 관련 `base/`/`luau-test/README.md` 캐비엇을 지우고 이 문서는 근거로 남김. **현재 3개**: `luau-test-first-run-2026-08-13.md`(첫 실측 라운드 전체 — 런타임 12개 통과, 구 `question.md` 0-Y의 1차 근거. **단 이 문서의 "콜백이 raw 값을 받으면 완전 클린" 판정은 아래 `type-recursion-issue/`가 뒤집었음**), `gcconn-trick-verification.md`(사용자가 Studio에서 직접 돌린 gcconn 트릭 부분 확인 — `10`의 A 섹션 앞부분만. **[2026-08-14 다섯 번째 세션]** 실측된 사실 자체는 그대로 유효하고 `canExecute(value)` 1-인자 재정정으로 오히려 더 중요해졌으나, 인용하던 `canBound`가 폐기돼 미확인 항목 목록을 새 모델 기준으로 갱신함 — 이중 바인딩 게이트/재바인딩 허용/`value` 쪽 복사 gcconn 판정/Instance userdata 동일성/B/C가 미확인), **`type-recursion-issue/`**(**[2026-08-13 열세 번째 세션 신설]** 0-Y 재실측 전체 — `REPORT.md` + `spikes/` 44개. 다른 audit 기록과 달리 **스크립트를 같이 둠**: 이 건의 근거가 "여러 formulation을 서로 대조한 것"이라 개별 파일을 직접 돌려야 판정이 재현되기 때문. 결론은 `base/typing-limits.md`로 승격됨) |
|
||||
| `audit/` | **[2026-08-13 신설]** `luau-test/` 등 스파이크를 실제로 돌려본 뒤 "무엇이 확인됐고 무엇이 아직 안 됐는지"를 기록하는 곳 — 스크립트/계획 자체가 아니라 **실측 결과**만 다룸. base/luau-test와 달리 부분 확인(일부만 통과)도 있는 그대로 기록, 완전히 해소되면 관련 `base/`/`luau-test/README.md` 캐비엇을 지우고 이 문서는 근거로 남김. **현재 4개**: `luau-test-first-run-2026-08-13.md`(첫 실측 라운드 전체 — 런타임 12개 통과, 구 `question.md` 0-Y의 1차 근거. **단 이 문서의 "콜백이 raw 값을 받으면 완전 클린" 판정은 아래 `type-recursion-issue/`가 뒤집었음**), `gcconn-trick-verification.md`(사용자가 Studio에서 직접 돌린 gcconn 트릭 부분 확인 — `10`의 A 섹션 앞부분만. **[2026-08-14 다섯 번째 세션]** 실측된 사실 자체는 그대로 유효하고 `canExecute(value)` 1-인자 재정정으로 오히려 더 중요해졌으나, 인용하던 `canBound`가 폐기돼 미확인 항목 목록을 새 모델 기준으로 갱신함 — 이중 바인딩 게이트/재바인딩 허용/`value` 쪽 복사 gcconn 판정/Instance userdata 동일성/B/C가 미확인), **`type-recursion-issue/`**(**[2026-08-13 열세 번째 세션 신설]** 0-Y 재실측 전체 — `REPORT.md` + `spikes/` 44개. 다른 audit 기록과 달리 **스크립트를 같이 둠**: 이 건의 근거가 "여러 formulation을 서로 대조한 것"이라 개별 파일을 직접 돌려야 판정이 재현되기 때문. 결론은 `base/typing-limits.md`로 승격됨), `fallback-xpcall-verification.md`(**[2026-08-14 신설]** `base/fallback-plan.md`의 `Traceback` 메커니즘 전부 확인 — 클로저 업밸류 배선/중첩 스택 캡처/`err: any`/`error(msg)` 위치 접두 10개 검증 전부 통과. 스크립트 1개뿐이라 재현용으로 같이 둠: `fallback-xpcall-spike.luau`) |
|
||||
| `tools/` | **[2026-08-13 아홉 번째 세션 신설]** 코퍼스 기계 점검 — `doc-check.py`가 깨진 파일/절 참조, README 색인 누락, 날짜 없는 시한부 주장("아직 안 돌려봄" 등), 미반영 ⚠️ 배너를 한 번에 훑음. **중대 변경 후 커밋 전에 돌릴 것**(`python3 .claude/tools/doc-check.py`) — 수동 감사에서 나온 발견의 대부분이 이 종류였고, 실제로 문서를 쪼개다 잘못 옮긴 참조를 이게 잡아냄. ERROR는 고치고 WARN은 판단 대상 |
|
||||
| `session/` | **[2026-08-11 신설]** 세션별 상세 로그 원문(시행착오·정정 전 서술 포함, `quadnomicon` 개발로그 소재용) — 루트 `CLAUDE.md`가 3196줄까지 불어나 성능 저하를 유발해서 분리함. 파일명 `YYYY-MM-DD-NN-slug.md`, CLAUDE.md의 "세션 히스토리" 절에서 각 항목이 여기로 링크. 항상 읽을 필요 없음 — 결정의 논의 과정이 궁금할 때만 |
|
||||
| `initreq/` | 프로젝트 착수 시 클론해둔 참고 레포(quad v1, fusion, vide, rbvm, tbox, code-docker) + PA님 실 코드(`artworks/`, 4차 라운드 교차검증 근거) + 원본 요청(`req.md`, `raw-userinput.md`) + `quad2-try`(이전에 시도했다 폐기한 v2 재작성 시도 — 리서치 완료, 결론은 `base/bind-system-plan.md`) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
|
||||
|
|
@ -51,6 +51,7 @@
|
|||
| `event-plan.md` | **[2026-08-13 아홉 번째 세션, `bind-system-plan.md`에서 분리 — 사용자가 직접 지목]** 이벤트 바인딩 — 핸들러가 self(Instance)를 **안** 받는다는 확정(Ref가 이미 커버, 이중 쓰기 경로 방지), 이벤트도 store-bind 가능하며 `false`를 넣으면 disconnect. 이벤트 *네이밍* 관례는 인스턴스 생성과 한 절에 섞여 있어 `bind-system-plan.md`에 남음, `GetPropertyChangedSignal`은 `onchange-plan.md`. **분리는 순수 이동** |
|
||||
| `brand-plan.md` | **[2026-08-13 아홉 번째 세션, `bind-system-plan.md`에서 분리]** `Brand` — 런타임 nominal 타입 판별 통합 메커니즘(`Brand.set`/`Brand.get`), `isState`를 10종 branded 타입 전부로 일반화. 동작/구현은 확정, **이름 `Brand` 자체만 용어 정리 대기**(`question.md` 1번). **분리는 순수 이동** |
|
||||
| `tween-plan.md` | **[2026-08-12 세션, `research/`에서 승격]** 값-레벨 `Tween<T>` 래퍼(PropertyHandler가 소비, 구 특수 bind key 모델은 `archive/tween-special-bind-key-reversed.md`). 3-상태 릴레이션 슬롯(`{Tween,Value}\|true\|nil`), `T'=T\|Tween<T>` 타입 치환. 옵션 값 모양은 `Info: TweenInfo?` 우선+편의 필드 폴백, override는 `Tween.Cancel`(기본)/`Tween.Finish` 2값. `Animate(info)`는 `Tween` opts를 `T\|State<T>`로 받아 `:Apply`로 꽂는 sugar. 자연완료 시 per-instance 북키핑은 정리 안 해도 됨으로 확정(목표값 도달 상태라 부작용 없음, Completed 이벤트 구독 장치는 오버엔지니어링으로 판단). `initValue`는 사용자가 직접 처리(에이전트 범위 제외) |
|
||||
| `fallback-plan.md` | **[2026-08-14 세션, `research/`에서 승격]** `Fallback`/`Traceback` — 컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 순수 슈가(`additional-primitives-plan.md`의 "Error Boundary는 빈 자리 아님" 결론 위에 얹힘). `Fallback`은 `pcall` 기반(trace 없음), `Traceback`은 `xpcall`+`debug.traceback` 기반(trace 항상 있음) — 플래그 대신 별도 함수로 분리(`Ref`/`PreRef`와 같은 패턴). `err: any`(Lua `error()`가 임의 값을 던질 수 있음, `error(msg)` 기본 호출의 위치 접두 캐비엇 포함) 확정. 패키지는 `quad-base`, 이름 확정. 메커니즘 실측은 `audit/fallback-xpcall-verification.md`. 구현 우선순위는 형제 백로그(`quad-mock`/`quad-debug`/`Operator`)와 동급, 맨 뒤 |
|
||||
|
||||
## `reference/` — 온디맨드 참고 자료 (2026-08-07 신설)
|
||||
|
||||
|
|
@ -72,7 +73,6 @@
|
|||
| `additional-primitives-plan.md` | **[2026-08-09 세 번째 세션, 전부 해소]** 마지막으로 남아있던 키 기반 동적 컬렉션 재조정도 `Slot:List(...)`로 확정되어 `base/slot-plan.md`로 승격 — 이 문서엔 새로 열린 설계 질문 없음, "빈 자리 아닌 것"/"문서화 백로그"/조사 소스 목록만 배경 자료로 유지 | 하 — 배경 리서치 기록용, 열린 결정 없음 |
|
||||
| `pre-implementation-audit.md` | M0 착수 직전 크리티컬 감사(2026-08-06 신설) — `base/` 전체를 모호성/지연결정리스크/단순화후보 세 렌즈로 재검토, 11개 우선순위1 + 11개 우선순위2 + 2개 단순화후보. **[2026-08-12 열일곱 번째 세션]** 우선순위1 11개 전원 해소 — 남은 건 `.claude/luau-test/` 스파이크 실측 확인뿐 | 상 — 설계는 전부 해소, `.claude/luau-test/` 스파이크 실측만 남음 |
|
||||
| `operator-sugar-plan.md` | **[2026-08-12 신설]** `Sum`/`Product`/`Not`/비트연산 등 `:Compute`/`:Apply`용 연산자 콤비네이터 슈가 — 메커니즘은 이미 확정된 계약(`Animate`와 동형 패턴) 재사용이라 확정, 네임스페이스 이름만 미정. **[2026-08-12 열아홉 번째 세션]** 서브 에이전트 외부 리서치로 다른 리액티브 라이브러리 선례와 대조 — `Operator`가 가장 강한 선례(Python `operator` 모듈), `Clamp`/`Min`/`Max`가 추가 후보로 부상, 비트연산·비교연산자·`Sub`/`Div`는 선례 전무로 드랍 후보, Debounce/Throttle은 `Blocker`와 다른 시간 기반 메커니즘이라 별도 질문으로 분리, `Filtered`의 Slot 안/밖 구분 판단이 ReactiveUI/SolidJS 선례로 뒷받침됨 — 최종 이름 결정은 여전히 사용자 몫. **[2026-08-13 세션, 두 번째]** Haskell 비교 리서치 중 `Alternative`(nil 대체값, coalesce류) 후보 신설 — 카탈로그 확정 규칙에 그대로 맞음, 이전엔 없던 게 확인됨 | 하 — 구현은 맨 마지막(순수 슈가, 없어도 무방, 함수 간 의존 없음), 사용자가 직접 후순위 지정 **[2026-08-13 여섯 번째 세션]** `State<State<T>|T>` → `State<T>` 평탄화 항목 신설(백로그) — `State<State<T>>`가 정상 동작하게 됐지만 `retractFrom`의 힌트가 직속 1단계에만 가서 깊은 중첩에선 깜빡임 방지가 꺼진다는 게 구체적 동기, 사용자 판단으로 "UB는 아니지만 원치 않는 방향". `Operator.*`가 아니라 `state:Flatten()` 메소드로 제공하는 게 맞아 보이며, **반환 노드가 동적 의존성을 갖는다는 난점**(quad가 의도적으로 비지원하기로 한 바로 그것)이 확정 전 최대 쟁점 |
|
||||
| `component-fallback-plan.md` | **[2026-08-14 신설]** 컴포넌트 함수를 감싸 에러 시 자동으로 플레이스홀더(fallback 컴포넌트)를 그려주는 유틸 `Fallback(original, onError)` — `additional-primitives-plan.md`가 이미 확정한 "Error Boundary는 빈 자리 아님, `pcall(MyComp,props)`로 충분"이라는 결론 위에 얹는 순수 슈가(`Operator`가 `:Compute`/`:Apply` 위에 얹힌 것과 같은 관계, 그 문서 재오픈 아님). **[같은 날 두 번째 세션]** `xpcall`+`debug.traceback` 메커니즘은 `component-fallback-xpcall-spike.luau`로 실측 확인 완료(클로저 업밸류 배선/중첩 스택 캡처 정상, `error(msg)` 기본 위치 접두 캐비엇 신규 확인) — `pcall` vs `xpcall` 정책, 패키지 배치, 이름은 여전히 미정 | 하 — 형제 백로그(`quad-mock`/`quad-debug`/`Operator`)와 동급, "quad 개발 상당 부분 끝난 뒤" |
|
||||
| `lifecycle-hooks-plan.md` | **[2026-08-14 신설]** `OnCreated`/`OnDestroyed` 생명주기 훅 — `OnCreated(fn)`은 `PreRef():Callback(fn)`, `OnDestroyed(fn)`은 `Effect(function() return fn end)`를 반환하는 순수 팩토리 함수라 새 타입/Dispatch 메커니즘이 전혀 필요 없음(호출 즉시 평가돼 기존 `PreRef`/`EffectHandle` 인스턴스로 사라짐), 여러 개 나란히 등록도 자연 지원. `OnRendered`는 현재 base에 없는 post-pass가 실제로 필요해 공짜가 아니라 **지금은 의도적으로 구현 안 함** — 거울상 `PostRef` 스케치(`PreRef`의 pre-pass와 대칭인 post-pass)만 백로그 후보로 남김 | 하 — 형제 백로그(`quad-mock`/`quad-debug`/`Operator`/`Fallback`)와 동급, "quad 개발 상당 부분 끝난 뒤" |
|
||||
| `v1-compat-plan.md` | v1 하위호환(compat) 레이어 — `quad-roblox-v1-compat` 패키지, v2→v1 단방향 브리지(`state:Observer()`+v1 프로퍼티 재대입), v2-in-v1/v1-in-v2 두 임베딩 방향의 기술 규칙까지 확정. quad2-try의 `quad-compat`은 빈 폴더로 실제 시도된 적 없었음을 확인 | 하 — Slot이 foreign Instance를 어떻게 다루는지만 Slot 코어 구현 시점까지 미결 |
|
||||
|
||||
|
|
|
|||
|
|
@ -582,6 +582,7 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
|
|||
| v1 내부 동작 스냅샷 | `reference/quad-v1-architecture.md` |
|
||||
| 트윈 — 값-레벨 `Tween<T>` 래퍼(2026-08-10)+옵션 값 모양·override 정책·`Animate` 콤비네이터·자연완료 북키핑(2026-08-12) 전부 확정, `base/`로 승격 | `base/tween-plan.md` |
|
||||
| quad2-try(폐기된 이전 시도) 리서치 — OOP 상속/커스텀 파서/Slot 스텁/`Pipe` COW 전부 죽은 접근으로 확인, 반복 조사 금지 | `base/bind-system-plan.md` |
|
||||
| `Fallback`/`Traceback`(2026-08-14) — 컴포넌트 에러 격리 유틸, `pcall`(가벼움)과 `xpcall`+`debug.traceback`(trace 항상 있음)으로 분리, `err: any` 확정(테이블 에러 등), 패키지·이름 전부 확정, `research/`에서 승격 | `base/fallback-plan.md` |
|
||||
|
||||
---
|
||||
전체 순서/우선순위는 루트 `CLAUDE.md`가 최종 소스 — 위 표는 힌트일 뿐 그쪽이
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- 스파이크: research/component-fallback-plan.md의 Fallback 메커니즘 의사코드가
|
||||
-- 실제 Luau에서 그대로 동작하는지 실측. 열린 질문:
|
||||
-- 스파이크: base/fallback-plan.md의 Traceback 메커니즘 의사코드가
|
||||
-- 실제 Luau에서 그대로 동작하는지 실측. 결과는 audit/fallback-xpcall-verification.md.
|
||||
-- "xpcall 에러 핸들러 배선의 실측 — 에러 핸들러 안에서 클로저 업밸류에 쓴
|
||||
-- 값이 바깥에서 제대로 보이는지, debug.traceback이 에러 시점 스택을 정확히
|
||||
-- 찍는지"
|
||||
|
|
@ -15,8 +15,8 @@ local function check(name, cond)
|
|||
end
|
||||
end
|
||||
|
||||
-- 문서의 의사코드 그대로 옮김
|
||||
local function Fallback(original, onError)
|
||||
-- 문서의 Traceback 의사코드 그대로 옮김
|
||||
local function Traceback(original, onError)
|
||||
return function(...)
|
||||
local trace: string? = nil
|
||||
local ok, resultOrErr = xpcall(original, function(err)
|
||||
|
|
@ -36,7 +36,7 @@ do
|
|||
return "OK:" .. props.name
|
||||
end
|
||||
local calledOnError = false
|
||||
local SafeWidget = Fallback(Widget, function(msg, trace)
|
||||
local SafeWidget = Traceback(Widget, function(msg, trace)
|
||||
calledOnError = true
|
||||
return "ERR"
|
||||
end)
|
||||
|
|
@ -51,7 +51,7 @@ do
|
|||
error("boom: " .. props.name)
|
||||
end
|
||||
local capturedMsg, capturedTrace = nil, nil
|
||||
local SafeWidget = Fallback(Widget, function(msg, trace)
|
||||
local SafeWidget = Traceback(Widget, function(msg, trace)
|
||||
capturedMsg = msg
|
||||
capturedTrace = trace
|
||||
return "PLACEHOLDER"
|
||||
|
|
@ -77,7 +77,7 @@ do
|
|||
return level2(props)
|
||||
end
|
||||
local capturedTrace = nil
|
||||
local SafeWidget = Fallback(level1, function(msg, trace)
|
||||
local SafeWidget = Traceback(level1, function(msg, trace)
|
||||
capturedTrace = trace
|
||||
return "PLACEHOLDER"
|
||||
end)
|
||||
|
|
@ -95,7 +95,7 @@ do
|
|||
error({ code = 42, reason = "custom" })
|
||||
end
|
||||
local capturedMsg = nil
|
||||
local SafeWidget = Fallback(Widget, function(msg, trace)
|
||||
local SafeWidget = Traceback(Widget, function(msg, trace)
|
||||
capturedMsg = msg
|
||||
return "PLACEHOLDER"
|
||||
end)
|
||||
|
|
@ -113,7 +113,7 @@ do
|
|||
return "PLACEHOLDER(" .. context .. "):" .. message
|
||||
end
|
||||
end
|
||||
local SafeWidget = Fallback(Widget, makeErrorHandler("someContext"))
|
||||
local SafeWidget = Traceback(Widget, makeErrorHandler("someContext"))
|
||||
local result = SafeWidget({})
|
||||
-- error("ctx-fail")의 기본 level(=1)이 "파일:줄: " 접두를 붙이므로
|
||||
-- message가 정확히 "ctx-fail"이 아님 — 아래 6번에서 별도로 실측/확정.
|
||||
|
|
@ -130,7 +130,7 @@ do
|
|||
local Widget = function()
|
||||
error("no-level-specified")
|
||||
end
|
||||
local SafeWidget = Fallback(Widget, function(msg) return msg end)
|
||||
local SafeWidget = Traceback(Widget, function(msg) return msg end)
|
||||
defaultLevelMsg = SafeWidget()
|
||||
end
|
||||
check("error(msg) 기본 호출 — onError가 받는 message에 위치 접두(\"파일:줄: \")가 자동으로 붙음",
|
||||
|
|
@ -144,7 +144,7 @@ do
|
|||
local Widget = function()
|
||||
error("no-level-specified", 0)
|
||||
end
|
||||
local SafeWidget = Fallback(Widget, function(msg) return msg end)
|
||||
local SafeWidget = Traceback(Widget, function(msg) return msg end)
|
||||
zeroLevelMsg = SafeWidget()
|
||||
end
|
||||
check("error(msg, 0) — 위치 접두 없이 순수 메시지만 onError에 전달됨",
|
||||
|
|
@ -152,7 +152,7 @@ do
|
|||
print(" zeroLevelMsg = " .. tostring(zeroLevelMsg))
|
||||
end
|
||||
|
||||
-- 6) original에 여러 인자를 넘기는 vararg 경로(문서 시그니처 (T...) -> Comp)
|
||||
-- 7) original에 여러 인자를 넘기는 vararg 경로(문서 시그니처 (Args...) -> Comp)
|
||||
do
|
||||
local Widget = function(a, b, c)
|
||||
if c == nil then
|
||||
|
|
@ -160,7 +160,7 @@ do
|
|||
end
|
||||
return a + b + c
|
||||
end
|
||||
local SafeWidget = Fallback(Widget, function(msg, trace)
|
||||
local SafeWidget = Traceback(Widget, function(msg, trace)
|
||||
return -1
|
||||
end)
|
||||
check("vararg 성공 경로", SafeWidget(1, 2, 3) == 6)
|
||||
54
.claude/audit/fallback-xpcall-verification.md
Normal file
54
.claude/audit/fallback-xpcall-verification.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# `Fallback`/`Traceback` — `xpcall`+`debug.traceback` 배선 실측 결과
|
||||
|
||||
**상태**: 전부 확인(2026-08-14). `base/fallback-plan.md`의 `Traceback`
|
||||
메커니즘 스케치가 실제 Luau에서 그대로 동작하는지 `luau` 스파이크
|
||||
(`fallback-xpcall-spike.luau`, 같은 폴더)로 검증 — 10개 검증 전부 통과,
|
||||
`luau-analyze`도 클린(타입 에러 0).
|
||||
|
||||
## 배경
|
||||
|
||||
`base/fallback-plan.md`(당시엔 research/ 초안) 단계에서 열어뒀던 질문:
|
||||
`xpcall`의 에러 핸들러 안에서 클로저
|
||||
업밸류에 쓴 값(`trace`)이 `xpcall` 리턴 이후에도 바깥에서 정상적으로
|
||||
보이는지, `debug.traceback`이 에러 시점 스택을 정확히 담는지 — 의사코드
|
||||
수준이라 Luau로 직접 부딪혀본 적 없었음.
|
||||
|
||||
## 확인된 것
|
||||
|
||||
1. **성공 경로** — `onError`가 아예 안 불리고 `base`의 원래 반환값이
|
||||
그대로 통과함.
|
||||
2. **실패 경로** — `onError`의 반환값이 최종 결과, 에러 메시지가
|
||||
`onError`에 정상 전달됨.
|
||||
3. **클로저 업밸류 배선(가장 핵심)** — `xpcall`의 에러 핸들러 안에서
|
||||
업밸류 `trace`에 쓴 값이 `xpcall` 리턴 후 `onError` 호출 시점에
|
||||
정상적으로 채워져 있음.
|
||||
4. **중첩 호출에서의 `debug.traceback`** — 3단 중첩(`level1→level2→level3`)
|
||||
호출에서도 `debug.traceback(nil, 2)`가 실패 지점(`level3`)까지 정확히
|
||||
담음. `level=2`가 익명 에러 핸들러 프레임 자체를 올바르게 스킵.
|
||||
5. **`err: any`** — 비-문자열 에러 값(`error({code=42})`류 table)도
|
||||
손실 없이 `onError`에 그대로 전달됨. 사용자가 별도로 Luau REPL에서
|
||||
`error({aa=true})` → `pcall`로 잡은 뒤 `b.aa == true`를 직접 재확인,
|
||||
스파이크 결과와 일치.
|
||||
6. **커링 관용구** — `onError` 자체를 클로저로 만들어 추가 컨텍스트를
|
||||
캡처하는 관용구가 `Fallback`/`Traceback` 쪽 손댈 것 없이 그대로 동작.
|
||||
7. **`error(msg)`의 기본 위치 접두(신규 발견)** — 레벨 지정 없이
|
||||
(Luau 기본 level=1) `error("메시지")`를 호출하면 `onError`가 받는
|
||||
`err`엔 `"파일:줄: 메시지"`처럼 위치 접두가 자동으로 붙음.
|
||||
`error(msg, 0)`으로 호출해야 접두 없는 순수 메시지가 옴 — quad가
|
||||
붙이는 게 아니라 Luau `error()` 자체의 기본 동작.
|
||||
8. **vararg 컴포넌트 시그니처** — `(Args...) -> Comp` 모양(여러 인자를
|
||||
받는 컴포넌트 함수)도 성공/실패 경로 둘 다 정상 동작.
|
||||
|
||||
## 실측 방법
|
||||
|
||||
`fallback-xpcall-spike.luau`(같은 폴더) — `base/fallback-plan.md`의
|
||||
`Traceback` 의사코드를 그대로 옮겨 10개 assert로 검증.
|
||||
`luau fallback-xpcall-spike.luau`로 실행,
|
||||
`luau-analyze fallback-xpcall-spike.luau`로 타입 체크(무출력 = 클린).
|
||||
|
||||
## 참고
|
||||
|
||||
`Fallback`(순수 `pcall`, trace 없음) 쪽은 `Traceback`보다 메커니즘이
|
||||
단순(에러 핸들러/업밸류 배선이 아예 없음)해서 별도 스파이크 없이도
|
||||
`pcall` 자체의 기본 동작(성공/실패 경로, `err: any` 통과)으로 충분히
|
||||
갈음됨 — 위 5번 확인이 그대로 적용됨.
|
||||
146
.claude/base/fallback-plan.md
Normal file
146
.claude/base/fallback-plan.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# `Fallback`/`Traceback` — 컴포넌트 에러 격리 유틸
|
||||
|
||||
**상태**: base — 확정(2026-08-14 세션). research/에서 신설(사용자 제안) →
|
||||
`luau` 스파이크로 `xpcall`/`debug.traceback` 배선
|
||||
실측(같은 날 두 번째 세션) → `Fallback`/`Traceback` 분리·정확한 제네릭
|
||||
시그니처·`err: any` 확정(같은 날 세 번째 세션, 사용자 확정)까지 한 흐름 —
|
||||
`base/`로 승격. **구현 우선순위는 여전히 맨 뒤**(아래 "우선순위" 절), 승격은
|
||||
설계가 다 정해졌다는 뜻이지 지금 만든다는 뜻이 아님.
|
||||
|
||||
## 동기
|
||||
|
||||
컴포넌트마다 개별적으로 `pcall`을 직접 감싸는 건 실용적이지 않음 — 매
|
||||
호출 자리마다
|
||||
`local ok, result = pcall(MyComp, props); if not ok then ... end`를
|
||||
손으로 반복해 쓰는 건 번거롭고 빠뜨리기도 쉬움. 대신 컴포넌트
|
||||
함수 하나를 받아서 "에러 나면 자동으로 플레이스홀더를 그려주는 버전"으로
|
||||
바꿔주는 아주 단순한 유틸이면 충분함 — 클린업 동작(언마운트/리소스 해제)이
|
||||
목적이 아니라, **실제 에러가 났을 때 디버깅이나 프로덕션 유저 리포트를
|
||||
편하게 만드는 게 유일한 목적**.
|
||||
|
||||
## 왜 새 프리미티브가 아닌가
|
||||
|
||||
`research/additional-primitives-plan.md`가 이미 "Error Boundary는 빈
|
||||
자리 아님 — `pcall(MyComp, props)`만으로 React Error Boundary와 같은
|
||||
격리 효과를 프레임워크 지원 없이 얻는다"고 확정해둔 결론을 뒤집는 게
|
||||
아니라, **그 결론 위에 얹는 순수 슈가**(그 문서를 다시 열 필요 없음) —
|
||||
`Operator` 콤비네이터(`research/operator-sugar-plan.md`)가 `:Compute`/
|
||||
`:Apply` 위에 얹힌 것과 같은 관계. `Fallback`/`Traceback` 둘 다 `original`을
|
||||
호출하고 결과를 그대로 돌려주는 순수 함수일 뿐, 디스패치/Store/Handler
|
||||
계층에 아무것도 새로 안 만듦.
|
||||
|
||||
## API — 왜 둘로 나뉘는가
|
||||
|
||||
```
|
||||
Fallback<OkComp, ErrComp, Args...>(
|
||||
base: (Args...) -> OkComp,
|
||||
onError: (err: any) -> ErrComp
|
||||
) -> (Args...) -> (OkComp | ErrComp)
|
||||
|
||||
Traceback<OkComp, ErrComp, Args...>(
|
||||
base: (Args...) -> OkComp,
|
||||
onError: (err: any, trace: string) -> ErrComp
|
||||
) -> (Args...) -> (OkComp | ErrComp)
|
||||
```
|
||||
|
||||
- **`Fallback`** — `pcall` 기반, 가벼움, `onError`엔 `err`만 넘어감(trace
|
||||
없음).
|
||||
- **`Traceback`** — `xpcall`+`debug.traceback` 기반, `onError`엔 `err`와
|
||||
함께 `trace: string`이 **항상**(옵셔널 아님) 넘어감.
|
||||
- **왜 플래그 하나로 안 합쳤는가**: quad는 이미 이런 갈림을 별도 타입/함수로
|
||||
가르는 쪽을 택해왔음(`Ref`/`PreRef`가 같은 예) — 항상 `xpcall`+
|
||||
`debug.traceback` 비용을 물지 않아도 되는 가벼운 경로를 자연스럽게 분리해
|
||||
둘 수 있고, `onError`의 시그니처 자체가 달라서(trace 유무) 타입으로도
|
||||
둘을 구분하는 게 더 정확함.
|
||||
- `OkComp`/`ErrComp`를 하나로 합친 `Comp`가 아니라 **독립 제네릭**으로 둔
|
||||
이유: 원래 컴포넌트와 에러 플레이스홀더가 다른 컴포넌트 타입일 수 있고,
|
||||
래핑된 함수의 실제 반환 타입은 정확히 `OkComp | ErrComp` 유니온이기
|
||||
때문(사용자 확정).
|
||||
- `onError` 자신이 추가 컨텍스트를 캡처하려고 커링된 클로저인 건 완전히
|
||||
자유 — `Fallback`/`Traceback`은 여기 관여하지 않음(아래 예시).
|
||||
|
||||
```lua
|
||||
local SafeWidget = Fallback(Widget, function(err)
|
||||
return ErrorPlaceholder { Message = err }
|
||||
end)
|
||||
|
||||
-- 추가 컨텍스트가 필요하면 onError 쪽에서 그냥 커링
|
||||
local function makeErrorHandler(context)
|
||||
return function(err, trace)
|
||||
return ErrorPlaceholder { Message = err, Context = context, Trace = trace }
|
||||
end
|
||||
end
|
||||
local SafeWidget2 = Traceback(Widget, makeErrorHandler(someContext))
|
||||
|
||||
-- 호출부는 원래 컴포넌트 대신 그대로 씀
|
||||
Frame { SafeWidget{ ... }, SafeWidget2{ ... } }
|
||||
```
|
||||
|
||||
### `err: any`임을 반드시 문서화 — 흔한 함정
|
||||
|
||||
Lua/Luau의 `error()`는 문자열이 아닌 **임의의 값**(테이블 등)을 던질 수
|
||||
있음 — `Fallback`/`Traceback` 둘 다 `err`를 `any`로 그대로 전달하고
|
||||
어떤 가공도 안 함. `error(msg)`를 레벨 지정 없이(Luau 기본 level=1)
|
||||
호출하면 `err`가 문자열이더라도 quad가 아무것도 안 붙였는데 Luau가
|
||||
자동으로 `"파일:줄: "` 위치 접두를 붙여서 옴 — `error(msg, 0)`으로
|
||||
호출해야 접두 없는 순수 메시지가 옴. **다들 `err`를 `string`으로 가정하고
|
||||
코드를 짜는 게 제일 흔한 실수라 문서화에서 최우선으로 경고할 것**(가공은
|
||||
`Fallback`/`Traceback`이 대신해주지 않음 — 가공까지 대신해주면 그게 또
|
||||
다른 매직이라는 원칙, `onError` 구현 몫으로 완전히 열어둠).
|
||||
|
||||
## 메커니즘 스케치
|
||||
|
||||
```lua
|
||||
function Fallback(base, onError)
|
||||
return function(...)
|
||||
local ok, resultOrErr = pcall(base, ...)
|
||||
if ok then
|
||||
return resultOrErr
|
||||
end
|
||||
return onError(resultOrErr)
|
||||
end
|
||||
end
|
||||
|
||||
function Traceback(base, onError)
|
||||
return function(...)
|
||||
local trace: string? = nil
|
||||
local ok, resultOrErr = xpcall(base, function(err)
|
||||
trace = debug.traceback(nil, 2)
|
||||
return err
|
||||
end, ...)
|
||||
if ok then
|
||||
return resultOrErr
|
||||
end
|
||||
return onError(resultOrErr, trace :: string)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
`Traceback`의 `debug.traceback(nil, 2)` 배선(클로저 업밸류가 `xpcall`
|
||||
리턴 이후에도 정상적으로 보이는지, 중첩 호출에서도 실패 지점까지
|
||||
스택을 정확히 담는지)과 `err: any`(테이블 에러도 손실 없이 통과하는지)는
|
||||
`luau` 스파이크로 실측 확인됨 — `audit/fallback-xpcall-verification.md`
|
||||
참고(스크립트: `audit/fallback-xpcall-spike.luau`).
|
||||
`research/debug-tooling-plan.md`가 이미 확인해둔 선례(Vide/Fusion 둘 다
|
||||
`xpcall`+`debug.traceback`으로 **에러 나는 순간에만** 스택을 찍는
|
||||
패턴)를 그대로 재사용 — 새 트레이싱 메커니즘을 발명하지 않음.
|
||||
|
||||
## 패키지 배치
|
||||
|
||||
`quad-base` — `base`를 그냥 호출하고 결과를 그대로 돌려주는 순수 함수라
|
||||
Store/Dispatch 어디에도 안 걸림, 엔진 지식이 전혀 필요 없음. `Operator`
|
||||
콤비네이터와 같은 결(사용자 확정).
|
||||
|
||||
## 이름
|
||||
|
||||
`Fallback`/`Traceback` 확정 — 낱개 함수 둘뿐이라 `Tag`/`Attribute`류
|
||||
네임스페이스가 필요했던 것과 달리 충돌 표면이 작다고 판단, 용어 정리
|
||||
대기열에 안 올리고 바로 점유(사용자 확정).
|
||||
|
||||
## 우선순위
|
||||
|
||||
**형제 백로그 항목들(`quad-mock`/`quad-debug`/문서 사이트/`Operator`)과
|
||||
동급, 맨 뒤 — "quad 개발 상당 부분 끝난 뒤"로 사용자가 못박은 후순위**
|
||||
(`CLAUDE.md` "지금 할 일" 4번). 이 문서가 `base/`로 승격된 건 설계가 다
|
||||
확정됐다는 뜻이지, 구현 착수 순서가 앞당겨졌다는 뜻이 아님 — `Operator`처럼
|
||||
순수 슈가라 없어도 quad 기능상 완전함(`pcall`을 직접 쓰면 되므로).
|
||||
|
|
@ -213,12 +213,6 @@
|
|||
사용자 확인 필요 — `base/attribute-plan.md` "열린 질문" 절.
|
||||
- `research/existing-instance-bind-plan.md` — 스코프 논의만 필요, 구현
|
||||
착수를 막지 않음.
|
||||
- **컴포넌트 에러 격리 유틸 `Fallback`(2026-08-14 신설, 사용자 제안)** —
|
||||
컴포넌트 함수를 감싸 에러 시 자동으로 플레이스홀더를 그려주는
|
||||
`pcall`/`xpcall` 래퍼. 기존 "Error Boundary는 빈 자리 아님"
|
||||
(`additional-primitives-plan.md`) 결론 위의 순수 슈가라 그 결론 자체는
|
||||
안 흔들림 — `pcall` vs `xpcall`+`debug.traceback`, 패키지 배치
|
||||
(`quad-base` 추정), 이름 전부 미정. 상세는 `research/component-fallback-plan.md`.
|
||||
- **`quad-debug` 세부 API 이름** — `research/debug-tooling-plan.md` 참고.
|
||||
채널 실현 가능성(BindableEvent/Function이 플러그인↔Play 중 게임 경계를
|
||||
넘는지)까지 사용자가 Studio에서 직접 실측 검증 완료 — 기술적 불확실성은
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
# 컴포넌트 에러 격리 유틸 — `Fallback`
|
||||
|
||||
**상태**: research — 사용자 제안(2026-08-14 세션)으로 신설, 착수 전
|
||||
백로그. `research/additional-primitives-plan.md`가 이미 "Error Boundary는
|
||||
빈 자리 아님 — `pcall(MyComp, props)`만으로 React Error Boundary와 같은
|
||||
격리 효과를 얻는다"고 확정해둔 결론을 뒤집는 게 아니라, **그 결론 위에
|
||||
얹는 순수 슈가**(그 문서를 다시 열 필요 없음) — `Operator` 콤비네이터
|
||||
(`research/operator-sugar-plan.md`)가 `:Compute`/`:Apply` 위에 얹힌 것과
|
||||
같은 관계. 우선순위는 그 형제 백로그 항목들(`quad-mock`/`quad-debug`/
|
||||
문서 사이트/`Operator`)과 동급 — "quad 개발 상당 부분 끝난 뒤"로 사용자가
|
||||
명시(`CLAUDE.md` "지금 할 일" 4번). **[2026-08-14 두 번째 세션]** 메커니즘
|
||||
자체(`xpcall`+`debug.traceback` 배선)는 `research/component-fallback-xpcall-spike.luau`로
|
||||
실측 확인 완료 — 아래 "메커니즘 스케치"/"열린 질문" 절 참고, 백로그
|
||||
우선순위 자체는 안 바뀜(여전히 착수 안 함).
|
||||
|
||||
## 동기 (사용자 원 메모)
|
||||
|
||||
컴포넌트마다 개별적으로 `pcall`을 직접 감싸는 건 실용적이지 않음 — 매
|
||||
컴포넌트 호출 자리마다
|
||||
`local ok, result = pcall(MyComp, props); if not ok then ... end`를
|
||||
손으로 반복해 쓰는 건 번거롭고 빠뜨리기도 쉬움. 대신
|
||||
컴포넌트 함수 하나를 받아서 "에러 나면 자동으로 플레이스홀더를 그려주는
|
||||
버전"으로 바꿔주는 아주 단순한 유틸이 있으면 충분함 — 클린업 동작
|
||||
(언마운트/리소스 해제)이 목적이 아니라, **실제 에러가 났을 때 디버깅이나
|
||||
프로덕션 유저 리포트를 편하게 만드는 게 유일한 목적**.
|
||||
|
||||
## 제안 API — `Fallback(original, onError) -> wrapped`
|
||||
|
||||
```
|
||||
Fallback(
|
||||
original: (T...) -> Comp,
|
||||
onError: (errorMessage: string, trace: string?) -> Comp
|
||||
) -> (T...) -> Comp
|
||||
```
|
||||
|
||||
`original`은 평범한 컴포넌트 함수(`function(props) return Frame{...} end`
|
||||
모양) 그대로. `Fallback`은 그걸 감싼 **같은 시그니처의 새 컴포넌트 함수**를
|
||||
돌려주므로, 호출부 입장에선 원래 컴포넌트를 쓰던 자리에 그대로 대체해
|
||||
끼워 넣을 수 있음(`MyComp{...}` → `Fallback(MyComp, OnMyCompError){...}`).
|
||||
|
||||
```lua
|
||||
local SafeWidget = Fallback(Widget, function(message, trace)
|
||||
return ErrorPlaceholder { Message = message }
|
||||
end)
|
||||
|
||||
-- 호출부는 Widget 대신 SafeWidget을 그대로 씀
|
||||
Frame { SafeWidget{ ... } }
|
||||
```
|
||||
|
||||
### 메커니즘 스케치 — 새 프리미티브 아님, `pcall`/`xpcall` 위의 순수 함수
|
||||
|
||||
```lua
|
||||
function Fallback(original, onError)
|
||||
return function(...)
|
||||
local trace: string? = nil
|
||||
local ok, resultOrErr = xpcall(original, function(err)
|
||||
trace = debug.traceback(nil, 2)
|
||||
return err
|
||||
end, ...)
|
||||
if ok then
|
||||
return resultOrErr
|
||||
end
|
||||
return onError(resultOrErr, trace)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**[2026-08-14 두 번째 세션, 실측 완료]** 위 의사코드 그대로 `luau`
|
||||
스파이크(`research/component-fallback-xpcall-spike.luau`)로 돌려 확인 —
|
||||
`xpcall` 에러 핸들러 안에서 업밸류 `trace`에 쓴 값이 `xpcall` 리턴 이후
|
||||
`onError` 호출 시점에 정상적으로 채워져 있고, `debug.traceback(nil, 2)`가
|
||||
익명 에러 핸들러 프레임을 건너뛰고 실패 지점까지의 실제 호출 스택(3단
|
||||
중첩까지 확인)을 정확히 담는 것도 확인됨. `research/debug-tooling-plan.md`가
|
||||
이미 확인해둔 선례(Vide/Fusion 둘 다 `xpcall`+`debug.traceback`으로 **에러
|
||||
나는 순간에만** 스택을 찍는 패턴)를 그대로 재사용 — 새 트레이싱
|
||||
메커니즘을 발명하지 않음.
|
||||
|
||||
**같은 실측에서 새로 확인된 캐비엇 — `error(msg)`의 기본 위치 접두**:
|
||||
컴포넌트 저자가 레벨 지정 없이 `error("메시지")`만 호출하면(Luau 기본
|
||||
level=1), `onError`가 받는 `errorMessage`엔 quad가 아무것도 안 붙였는데도
|
||||
`"MyComp.luau:42: 메시지"`처럼 **파일:줄 접두가 이미 붙어서** 옴 —
|
||||
`error(msg, 0)`으로 호출해야 접두 없는 순수 메시지가 옴. `Fallback` 쪽
|
||||
코드가 만드는 게 아니라 Luau `error()` 자체의 기본 동작이라 `Fallback`이
|
||||
따로 손댈 지점은 아니지만, 아래 "프로덕션에서의 동작" 열린 질문(화면에
|
||||
그대로 노출할지)에 실제로 영향을 주므로 그 항목에도 반영.
|
||||
|
||||
### `ErrorComp`가 추가 상태가 필요하면 — 커링 (사용자 명시)
|
||||
|
||||
`onError` 자체가 클로저이므로, 별도 API 없이 그냥 커링으로 풀림:
|
||||
|
||||
```lua
|
||||
local function makeErrorHandler(context)
|
||||
return function(message, trace)
|
||||
return ErrorPlaceholder { Message = message, Context = context }
|
||||
end
|
||||
end
|
||||
|
||||
local SafeWidget = Fallback(Widget, makeErrorHandler(someContext))
|
||||
```
|
||||
|
||||
`Fallback` 자신은 이런 경우를 특별히 신경 쓸 필요 없음 — `onError`가
|
||||
이미 평범한 함수이기 때문.
|
||||
|
||||
## 왜 기존 "Error Boundary는 빈 자리 아님" 결론과 안 부딪히는가
|
||||
|
||||
`research/additional-primitives-plan.md`의 결론은 "새 프리미티브가 필요
|
||||
없다"는 것이었지 "지금 이대로 편하다"는 게 아니었음 — `Fallback`은 그
|
||||
문서가 이미 지목한 정확히 같은 메커니즘(`pcall(MyComp, props)`)을 감싸는
|
||||
얇은 편의 함수일 뿐, 디스패치/Store/Handler 계층에 아무것도 새로 안 만듦.
|
||||
`Operator` 콤비네이터가 `:Compute`/`:Apply` 위에서 그랬던 것과 동일한
|
||||
관계 — 그 문서를 다시 열 필요 없음.
|
||||
|
||||
## 열린 질문
|
||||
|
||||
- **`pcall` vs `xpcall`+`debug.traceback`**: 스택 트레이스까지 항상
|
||||
캡처할지, 아니면 가벼운 `pcall`(에러 메시지만)을 기본으로 하고 트레이스는
|
||||
옵션(`onError`가 2번째 인자를 안 받으면 그냥 안 계산)으로 둘지. Roblox
|
||||
`debug` 라이브러리가 제한적이라는 건 이미 확인돼 있어서(`debug-tooling-plan.md`)
|
||||
부담은 크지 않음 — 여전히 미정인 건 "항상 캡처 vs 옵션"이라는 정책
|
||||
판단뿐, 메커니즘 자체는 아래처럼 실측 완료.
|
||||
- **[2026-08-14 두 번째 세션, 해소]** ~~`xpcall` 에러 핸들러 배선의
|
||||
실측~~: `luau` 스파이크(`research/component-fallback-xpcall-spike.luau`,
|
||||
10개 검증 전부 통과)로 확인 — 에러 핸들러 안에서 클로저 업밸류에 쓴
|
||||
값이 바깥에서 정상적으로 보이고, 3단 중첩 호출까지 `debug.traceback`이
|
||||
실패 지점을 정확히 담음. 부수적으로 `error(msg)` 기본 호출이 위치
|
||||
접두(`"파일:줄: "`)를 자동으로 붙인다는 캐비엇을 새로 확인(아래
|
||||
"프로덕션에서의 동작" 항목에 반영).
|
||||
- **패키지 배치**: `original`을 그냥 호출하고 결과를 그대로 돌려주는
|
||||
순수 함수라 Store/Dispatch 어디에도 안 걸림 — `quad-base`(엔진 무종속)가
|
||||
자연스러워 보임, `Operator`와 같은 결. 최종 확인 필요.
|
||||
- **이름**: `Fallback`이 흔한 단어라 top-level 노출 시 충돌 위험 — 다른
|
||||
가칭들과 같은 용어 정리 대기열로 볼지, 아니면 이 유틸 하나뿐이라
|
||||
네임스페이스 없이 top-level 함수로 둬도 괜찮을지(`Tag`/`Attribute`류
|
||||
네임스페이스가 필요했던 건 그 안에 여러 이름이 몰려서였고, 이건 낱개
|
||||
함수 하나뿐이라 충돌 표면이 작음).
|
||||
- **프로덕션에서의 동작**: 에러 메시지/트레이스를 유저에게 보이는 화면에
|
||||
그대로 노출할지, 아니면 로그로만 보내고 화면엔 일반화된 메시지만
|
||||
보여줄지는 `onError` 구현(사용자 코드) 몫으로 완전히 열어두는 게 맞아
|
||||
보임 — `Fallback` 자체는 raw 에러 정보를 그대로 넘기기만 하고 가공은
|
||||
안 함(가공까지 대신해주면 그게 또 다른 매직). **[2026-08-14 두 번째
|
||||
세션 추가]** 이 raw 정보엔 `error(msg)`(레벨 지정 없는 기본 호출)의
|
||||
자동 위치 접두("파일:줄: ")도 포함됨이 실측으로 확인됨 — 화면에 그대로
|
||||
노출하고 싶지 않은 저자는 `error(msg, 0)`으로 직접 접두를 꺼야 함,
|
||||
`Fallback`이 대신 벗겨주지는 않음(문서화로 안내할 사항, 위 "가공 안
|
||||
함" 원칙과 일치).
|
||||
- 그 외 확정된 결정 없음 — 착수 시점에 위 항목들을 순서대로 확인.
|
||||
|
||||
## 우선순위
|
||||
|
||||
**형제 백로그 항목들과 동급, 맨 뒤.** `Operator`처럼 순수 슈가라 없어도
|
||||
기능 격차 없음(`pcall`을 직접 쓰면 되므로, `additional-primitives-plan.md`가
|
||||
이미 확인한 그대로) — 편의성 문제일 뿐.
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
**상태**: research — 사용자 제안(2026-08-14 세션)으로 신설, 착수 전
|
||||
백로그. 이미 확정된 `PreRef`/`Ref`(`base/ref-plan.md`)와
|
||||
`Effect`(`base/effect-plan.md`) 프리미티브 위에 얹는 순수 슈가 후보 —
|
||||
`research/component-fallback-plan.md`의 `Fallback`이
|
||||
`base/fallback-plan.md`의 `Fallback`/`Traceback`이
|
||||
`additional-primitives-plan.md`의 기존 결론 위에 얹혔던 것과 같은 관계,
|
||||
이 문서도 그 프리미티브들의 확정 사항을 하나도 안 뒤집음. 우선순위는 그
|
||||
형제 백로그들(`quad-mock`/`quad-debug`/`Operator`/`Fallback`)과 동급 —
|
||||
|
|
|
|||
30
.claude/session/2026-08-14-03-fallback-traceback-promoted.md
Normal file
30
.claude/session/2026-08-14-03-fallback-traceback-promoted.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# 2026-08-14 세 번째 세션 — `Fallback`/`Traceback` 승격
|
||||
|
||||
`research/component-fallback-plan.md`를 `base/fallback-plan.md`로 승격.
|
||||
해소된 내용:
|
||||
|
||||
- **`Fallback`/`Traceback`으로 분리** — `pcall` 기반 `Fallback`(trace
|
||||
없음)과 `xpcall`+`debug.traceback` 기반 `Traceback`(trace 항상 있음)을
|
||||
플래그 하나 대신 별도 함수 둘로 확정(`Ref`/`PreRef`와 같은 패턴).
|
||||
- **정확한 시그니처 확정**:
|
||||
`Fallback<OkComp, ErrComp, Args...>(base: (Args...) -> OkComp, onError: (err: any) -> ErrComp) -> (Args...) -> (OkComp | ErrComp)`,
|
||||
`Traceback`은 `onError`가 `(err: any, trace: string)`을 받는 것만 다름.
|
||||
- **`err: any` 확정** — Lua `error()`가 임의 값(테이블 등)을 던질 수 있음을
|
||||
사용자 REPL 확인과 스파이크 결과로 재확인, `error(msg)` 기본 호출의
|
||||
위치 접두("파일:줄: ") 캐비엇도 같이 문서화.
|
||||
- **패키지는 `quad-base`**(사용자 확정), **이름은 `Fallback`/`Traceback`으로
|
||||
점유**(사용자 확정, 용어 정리 대기열 안 올림).
|
||||
- 열려있던 질문 전부 해소 — 남은 건 구현 자체뿐(우선순위는 그대로 맨 뒤).
|
||||
|
||||
## 반영
|
||||
|
||||
- `base/fallback-plan.md` 신설(승격), `research/component-fallback-plan.md`
|
||||
삭제.
|
||||
- 스파이크 스크립트를 `research/`에서 `audit/fallback-xpcall-spike.luau`로
|
||||
이동, 내부 함수명도 `Fallback`→`Traceback`으로 정정(실제 검증 대상과
|
||||
일치시킴), 재실행으로 통과 재확인.
|
||||
- `audit/fallback-xpcall-verification.md` 신설(실측 결과 기록).
|
||||
- `README.md`(base/research/audit 표), `question.md`(해소 항목 제거),
|
||||
`archive/question-resolved.md`(요약 테이블에 추가), `CLAUDE.md`(4번
|
||||
백로그 목록), `research/lifecycle-hooks-plan.md`(경로 참조 정정)
|
||||
전부 동기화.
|
||||
39
CLAUDE.md
39
CLAUDE.md
|
|
@ -239,19 +239,20 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
|
|||
구조(초심자/api/심화/`quadnomicon` 4축 + 콘텐츠 맵), `Operator` 콤비네이터
|
||||
슈가(`Sum`/`Product`/`Not`/비트연산 등 `:Compute`/`:Apply`용 — 메커니즘은
|
||||
확정, 네임스페이스 이름만 미정, 구현은 순수 슈가라 맨 마지막), 컴포넌트
|
||||
에러 격리 유틸 `Fallback`(**[2026-08-14 신설]** 컴포넌트 함수를 감싸 에러
|
||||
시 자동으로 플레이스홀더를 그려주는 `pcall`/`xpcall` 래퍼 — 기존
|
||||
"Error Boundary는 빈 자리 아님" 결론 위의 순수 슈가,
|
||||
`research/component-fallback-plan.md`), 생명주기 훅
|
||||
에러 격리 유틸 `Fallback`/`Traceback`(**[2026-08-14 세션, 설계 확정 —
|
||||
`research/`에서 `base/fallback-plan.md`로 승격]** `pcall` 기반
|
||||
`Fallback`과 `xpcall`+`debug.traceback` 기반 `Traceback`으로 분리,
|
||||
`err: any` 확정, 패키지·이름 전부 확정 — **설계만 끝났을 뿐 구현
|
||||
우선순위는 그대로 맨 뒤**), 생명주기 훅
|
||||
`OnCreated`/`OnDestroyed`(**[2026-08-14 신설]** `PreRef`/`Effect`를
|
||||
반환하는 순수 팩토리 함수 슈가, `research/lifecycle-hooks-plan.md` —
|
||||
`OnRendered`는 base에 없는 post-pass가 필요해 공짜가 아니라 지금은
|
||||
의도적으로 구현 안 함, 거울상 `PostRef` 스케치만 백로그 후보) — 전부
|
||||
"quad 개발 상당 부분 끝난 뒤"로 사용자가 못박은 후순위. 상세는
|
||||
`.claude/README.md`의 `research/` 표(`debug-tooling-plan.md`/
|
||||
`documentation-plan.md`/`documentation-content-map.md`/
|
||||
`framework-comparison-findings.md`/`operator-sugar-plan.md`/
|
||||
`component-fallback-plan.md`/`lifecycle-hooks-plan.md`).
|
||||
`.claude/README.md`의 `base/` 표(`fallback-plan.md`)와 `research/` 표
|
||||
(`debug-tooling-plan.md`/`documentation-plan.md`/
|
||||
`documentation-content-map.md`/`framework-comparison-findings.md`/
|
||||
`operator-sugar-plan.md`/`lifecycle-hooks-plan.md`).
|
||||
5. 자율 작업 루프/스케줄 설정 여부는 사용자 결정 대기 중
|
||||
(`HUMAN_TODO.md` 2번 항목).
|
||||
|
||||
|
|
@ -1130,7 +1131,8 @@ claim**으로 확정. 이걸로 마지막 게이트가 열려 **0-A(하강 diff
|
|||
`research/additional-primitives-plan.md`가 이미 확정한 "Error Boundary는
|
||||
빈 자리 아님, `pcall(MyComp,props)`로 충분"이라는 결론을 뒤집는 게 아니라
|
||||
그 위에 얹는 순수 슈가(`Operator`가 `:Compute`/`:Apply` 위에 얹힌 것과
|
||||
같은 관계)로 판단해 새 `research/component-fallback-plan.md` 신설 —
|
||||
같은 관계)로 판단해 새 research 문서 신설(세 번째 세션에
|
||||
`base/fallback-plan.md`로 승격, 이하 경로는 신설 당시 기준) —
|
||||
`xpcall`+`debug.traceback` 메커니즘 스케치, 커링 관용구, 열린 질문(pcall
|
||||
vs xpcall, 패키지 배치, 이름, 프로덕션 동작) 정리, 설계 확정은 아직 없음.
|
||||
부수적으로 워크트리가 계획 문서 없이 빈 채로 시작되는 걸 발견 —
|
||||
|
|
@ -1145,12 +1147,25 @@ vs xpcall, 패키지 배치, 이름, 프로덕션 동작) 정리, 설계 확정
|
|||
**2026-08-14 두 번째 세션 — `Fallback` 메커니즘 `xpcall` 실측 확인**
|
||||
(`session/2026-08-14-02-fallback-xpcall-spike-verified.md`)
|
||||
직전 세션이 열어둔 "`xpcall` 에러 핸들러 배선의 실측 필요"를 새 워크트리에서
|
||||
`luau` 스파이크(`research/component-fallback-xpcall-spike.luau`)로 확인 —
|
||||
`luau` 스파이크(현재 `audit/fallback-xpcall-spike.luau`로 이동)로 확인 —
|
||||
클로저 업밸류 배선, 3단 중첩 `debug.traceback` 캡처 등 10개 검증 전부
|
||||
통과. 부수 발견으로 `error(msg)` 기본 호출(level=1)이 위치 접두
|
||||
("파일:줄: ")를 자동으로 붙인다는 캐비엇을 새로 확인해 문서에 반영 —
|
||||
`research/component-fallback-plan.md`의 해당 열린 질문을 해소로 표시,
|
||||
백로그 우선순위 자체는 그대로.
|
||||
당시 research 문서(현재 `base/fallback-plan.md`)의 해당 열린 질문을
|
||||
해소로 표시, 백로그 우선순위 자체는 그대로.
|
||||
|
||||
**2026-08-14 세 번째 세션 — `Fallback`/`Traceback` 승격**
|
||||
(`session/2026-08-14-03-fallback-traceback-promoted.md`)
|
||||
사용자가 `Fallback`/`Traceback`으로 분리(`pcall` 기반 vs `xpcall`+trace
|
||||
기반), 정확한 제네릭 시그니처(`Traceback`은 `onError`가 `trace: string`도
|
||||
받는 것만 `Fallback`과 다름 — 전체 시그니처는 `base/fallback-plan.md`
|
||||
참고), `err: any`(사용자 REPL로 테이블 에러 통과 재확인), 패키지
|
||||
(`quad-base`), 이름(`Fallback`/`Traceback` 그대로 점유)까지 한 번에
|
||||
확정 — 남은 열린 질문이 없어져 research/ 초안을 `base/fallback-plan.md`로
|
||||
승격(파일 이동), 스파이크는
|
||||
`audit/fallback-xpcall-spike.luau`로 옮기며 내부 함수명도 `Traceback`으로
|
||||
정정. `README.md`/`question.md`/`archive/question-resolved.md`/
|
||||
`research/lifecycle-hooks-plan.md`의 상호 참조 전부 동기화.
|
||||
|
||||
**2026-08-14 네 번째 세션 — `ProcessedPreRef` 신설로 Length/Offset 등록
|
||||
갭 해소, `PostRef` 완전 대칭화**
|
||||
|
|
|
|||
Loading…
Reference in a new issue