이 package 문서는 구현자와 Adapter 작성자를 위한 **비규범 review checklist**다. 규범의 원문과 rule ID는 ARCHITECTURE.md 가 소유한다. 충돌하거나 해석이 갈리면 이 목록을 수정하며 새 규칙을 여기서 만들지 않는다.
1. Composition
- Composition Root만 Host Provider, MISE factory와 Adapter Provider를 함께 import한다.
- Provider는 등록만 하고 DOM·renderer·asset을 만들지 않는다.
- Restricted Container는 Registry compile 뒤 Application Factory 안에서만 graph를 조립한다.
- Container는 generic token과
value | singleton | scoped | transientlifetime만 제공하며 reflection·decorator·string key·auto wiring을 제공하지 않는다. - Container와
resolve()를 Scene·Driver·Object·Port context에 전달하지 않는다. - Container cache는 객체 identity만 관리한다. listener·GPU·DOM·frame lease 정리는
ResourceScope가 소유한다. - Renderer는 필수다. Motion·Navigation·Scroll은 필요할 때만 등록하며 부재 시 Null Port가 사용된다.
- 모든 Provider 등록 후
Registry.compile()을 한 번 호출한다. 이 호출이 등록을 닫고 immutable Plan을 생성한다. - boot 이후 registration과 runtime Service Locator를 금지한다.
- Plan compile은 빈 ID·empty Experience, 중복 Surface·View·Track·Scene ID,
끊어진 Surface/View 참조, 한 View의 중복 Track, View가 없는 Surface, 둘 이상의 default Surface, isolated Surface의 복수/부분 View, 유효하지 않은 mode·target·clear·root·activation, 미등록 Driver와 유효하지 않은 built-in Driver option을 거부한다.
- Plan은 Experience·Surface·View·Track·Scene·Drive·Page를 detached
snapshot으로 복제하고 nested Driver option까지 freeze한다.
- ID는 128자 safe identifier, selector는 control character 없는 512자 이하로 제한한다.
- Plan은 Experience 64, Page 64, Experience당 Surface 8·View 32·Track 32,
Track당 Scene 64, Scene당 Object factory 256을 상한으로 한다.
- compile 이후 caller의 원본 definition 변경이 Plan을 바꾸면 실패다.
- Plan compile 실패는 renderer 생성 전 발생해야 한다.
2. Public API
- Root runtime value API는
createMise,createMiseLogger,resolveBrowserLogLevel,MiseError,MiseAggregateError,defineProvider,defineExperience,defineSurface,defineView,defineTrack,definePage,defineScene,defineObjectFactory,defineDriver,scroll,auto로 제한한다. - Port와 definition은 type-only export한다.
- public 함수·Port signature가 참조하는 named type은 같은 public entry에서 export한다.
- Kernel constructor와 mutable Registry를 export하지 않는다.
- Adapter와 Playground는 명시적 subpath만 사용한다.
- default export와 wildcard export를 금지한다.
- public signature는 vendor concrete type을 필요한 범위 이상 노출하지 않는다.
- 모든 export type·class·함수와 public member는 TSDoc summary를 가진다.
- 호출 가능한 parameter는
@param, 의미 있는 반환값은@returns, public failure contract는@throws로 설명한다. - committed API report의
(undocumented)와Warning:은 각각 0이어야 한다.
3. Type
strict,exactOptionalPropertyTypes,noUncheckedIndexedAccess,isolatedDeclarations를 package source와 public declaration Gate에 적용한다.skipLibCheck: true는 vendor declaration 재검사 비용만 줄인다. MISE source·생성 declaration·tarball consumer 검증을 생략하는 근거로 사용할 수 없다.- public
any를 금지하고 외부 입력은unknown에서 검증한다. - Port는 작은 구조적 interface로 둔다.
- 상태는 boolean 조합 대신 discriminated union으로 표현한다.
- 내부 ID는 실제 오교환 위험이 있을 때만 opaque type을 쓴다.
- definition helper는 literal ID와 readonly tuple inference를 유지한다.
- 계약 type은
src/types/*Types.ts, definition helper 구현은src/definitions/Define.ts가 소유하고Contracts.ts는 re-export만 수행한다.
4. Lifecycle
- 단순 Experience의 Scene order는 기존 readonly
scenes가 소유한다. - Stage Experience는 Track의 readonly
scenes가 순서를 소유한다. - Track은 하나의 View와 독립 SceneChanger·Driver session을 소유한다.
- Track끼리 Scene instance, Changer 또는 Driver를 직접 참조하지 않는다.
- 독립 preload·abort·dispose가 필요하면 Scene, 같은 자원 수명의 변화면 Segment다.
- Cue 순서는
before → action → after다. - Scene
create와 네 hook은 같은 전환의 AbortSignal을 사용한다. - Scene이 제품 Object factory를 선언하면 Object Host만 해당 factory를 생성할 수 있다.
- Object factory는 Scene child scope, transition signal과 immutable create context를 받는다.
- factory 완료 전 abort되면 반환 Object와 child scope를 즉시 rollback한다.
- Scene은 Object의
dispose()를 직접 호출하지 않는다. Object Host가 역순·멱등 정리를 소유한다. - 네 hook은
void | Promise<void>를 반환하며 Changer가 await한다. - 모든 async prepare는 AbortSignal과 transition epoch를 확인한다.
- commit은 Changer 한 곳에서만 수행한다.
- pre-commit 실패 시 이전 active를 유지한다.
- 늦게 완료된 GLB 결과는 abort를 확인하고 즉시 폐기한다.
- Experience activation이 commit되지 않으면 candidate Driver를 전부 정리하고 이전 Driver session을 유지한다.
- post-commit 실패 시 새 active를 임의 rollback하지 않는다.
- post-commit hook 대기 중 더 최신 전환이 실패해도 이미 commit된 Scene의 성공 결과를 stale로 오판하지 않는다.
- dispose는 중복 호출 안전해야 한다.
5. Frame
- FrameLoop과 RAF owner는 Application에 각각 하나다.
- renderer와 canvas owner는 실제 Surface마다 각각 하나다.
- View와 Track은 Renderer나 RAF를 새로 만들지 않는다.
- Scene·Effect·Adapter는 자체 RAF를 생성하지 않는다.
- Clock은 단일 FrameLoop가 RAF timestamp에서만 읽는다. Three.js
Clock과 별도 TimeSource Port는 사용하지 않는다. - 첫 frame과 resume 직후 delta는 0이다.
rawDelta는 RAF 간 실제 초,delta는 최대 0.1초로 clamp한 simulation 초,elapsed는 clamp delta 누적,frame은 전달 순번이다.- negative·non-finite timestamp는 안전한 0 또는 stable error로 처리하고 background catch-up을 금지한다.
- update와 render phase를 분리한다.
- update가 끝난 뒤 Surface별 View order로 render pass를 실행한다.
- 보이지 않는 View는 Scene frame과 draw call을 실행하지 않는다.
- 정지 상태에서는 continuous frame lease를 모두 반납한다.
- Debug Port가 비활성이면 frame hot path에서 renderer stats·Health report·Debug snapshot을 만들지 않는다.
6. Driver
- Auto Driver 진행은 절대 RAF
time이 아니라 실제 sample 동안의 simulation
delta
만 누적한다. visibility·BFCache suspension과 reduced-motion pause 뒤 숨겨진 시간을 따라잡지 않는다.
- live reduced-motion 전환은 normal timeline을 보존하고
shortentimeline은
진입마다 0에서 별도로 시작한다.
- custom Driver 설정은 순환 참조와 지원 depth/node budget을
MISE_DRIVER_INVALID
로 거부한다.
- Driver는 입력을
progress,velocity,direction,delta로 정규화한다. DriveSample은 현재 frame에서만 유효한 snapshot이다. consumer는 참조 동일성에 의존하거나 다음 frame까지 보관하지 않는다.- Scroll Driver는 camera나 Scene object를 직접 알지 않는다.
- Driver selector는 Experience root 안에서만 해석한다. fixed Surface 밖의 document-flow trigger가 필요하면 Host가
initialExperienceRoot: "body"를 명시하며 기본값은surface다. - Auto Driver는 동일한 Scene instance 계약을 사용한다.
- Auto Driver는
pause | complete | shorten(duration)reduced-motion policy를 필수로 선언한다. - reduced-motion
shorten은 한 번 완료하며 원본 loop를 반복하지 않는다. - reduced-motion 상태는 mount 시 snapshot이 아니라
MediaQueryListchange를 반영하는 live readonly state다. - 자동 Scene 전환 실패는 같은 선택을 frame마다 재시도하지 않는다. active 선택으로 복귀하거나 명시적 refresh가 있어야 retry한다.
- custom Driver는 등록된 factory를 통해서만 생성한다.
- custom Driver option은 immutable JSON형 data만 허용하고 class instance·함수·DOM object·non-finite number를 거부한다.
- Scroll·custom Driver는 공통
sample.active로 Scene 후보를 제공하고,
동시에 active이면 Experience 선언 순서상 마지막 후보가 이긴다.
- Scene 전환 시 Driver listener와 lease를 Scope가 정리한다.
7. Renderer·Viewport·Quality
- viewport/scissor pass 다음 no-pass render는 전체 drawing-buffer viewport를
복원한 뒤 렌더링한다.
- Renderer는 Port 뒤에 둔다.
- 하나의 compositor Surface는 viewport·scissor가 다른 여러 View를 순서대로
렌더할 수 있다.
- isolated Surface는 독립 canvas·Renderer·context를 소유하지만 Application의
FrameLoop과 Scroll Port를 공유한다.
- View clear policy는
all | depth | none중 하나이며 암묵적인 전역 clear에
의존하지 않는다.
- resize는 CSS size와 device pixel ratio를 분리한다.
- pixel ratio는 viewport 크기, coarse-pointer capability와 drawing-buffer
pixel 예산을 사용하는 Quality 정책으로 clamp한다.
- resize·VisualViewport·screen orientation·pointer capability 변경은 한
animation frame으로 coalesce한다.
- Quality는
high → medium → low단계 저하와 역방향 회복을 모두 지원한다. - Quality는 분리된 threshold, sample-window cooldown과 background delta 필터를 사용한다.
- UA 문자열 기반 iOS/Samsung 분기 대신 capability와 측정을 사용한다.
- WebGL context loss 시 해당 Surface의 pass만 중지하고 restore/fallback
transaction을 수행한다.
- context restore는 해당 Surface에 연결된 Track Scene만 재생성한다.
- 한 Surface의 장애가 다른 Surface나 전역 FrameLoop를 suspend하면 실패다.
- Scene 전환 전 필요한 program은 가능한 범위에서 사전 compile한다.
8. Resource·Asset
- Application·Runtime·Surface·Scope dispose는 일부 cleanup이 실패해도 나머지를
모두 시도하고 마지막에 MiseAggregateError
로 보고한다.
- 모든 자원은 own·borrow·lease 중 하나다.
- Scope child와 cleanup은 역순으로 정리한다.
- listener, observer, timer, frame lease와 animation context도 자원이다.
- Scene graph에서 제거한 것만으로 dispose 완료로 보지 않는다.
- shared asset은 ref-count lease로 관리한다.
- 하나의 GLB owner가 공유 geometry·material·texture·skeleton과 owned
ImageBitmap을 정리할 때 각 자원을 정확히 한 번 dispose/close한다. - 실제 asset consumer 전에는 AssetStore abstraction을 만들지 않는다.
- GLB를 기본 교환 형식으로 사용하고 decoder는 Adapter에서 주입한다.
- runtime loader는 query/hash 없는 same-origin
.glb, 32 MiB byte budget, GLB 2.0
header와 embedded-only resource policy를 강제한다.
- GLB 외부 buffer·image URI, redirect와 unsupported media type을 허용하면 실패다.
- Host asset catalog는 MISE adapter 호출 전에도 제품 허용 경로·확장자를 검증한
branded URL 또는 동등한 nominal type을 사용한다. 일반 texture loader는 Host 플랫폼 경계가 소유하고 lifecycle abort 뒤 pending·late 결과를 정리한다.
- Provider·Driver·Port·Object factory와 Shader module은 build에 포함된 신뢰 코드다.
remote module URL과 runtime JSON module path를 실행하지 않는다.
9. Shader
- effect별
.vert.glsl,.frag.glsl, TS owner를 분리한다. - uniform 변경은 Effect owner method를 통해서만 한다.
- frame 중 material define과 program variant를 바꾸지 않는다.
- compile 실패는 stable error code와 제품 fallback으로 처리한다.
- Effect dispose가 material, owned texture와 listener를 정리한다.
- arbitrary query나 사용자 입력으로 shader module을 import하지 않는다.
10. DOM·Motion·Navigation
- 단순 Host의 Surface는 기존
[data-mise-surface]를 hydrate하거나 native
DOM으로 한 번만 생성하며 ID default
로 정규화한다.
- Stage Surface는
target.kind: "default" | "selector"로 canvas를 resolve한다. - View는
target.kind: "surface" | "selector"로 Surface 전체 또는 section
anchor를 가리킨다.
- DOM boundary만 Scroll·refresh·Surface viewport 변경 시 Surface snapshot과
View rect를 batch 측정한다. 정지된 continuous frame에서 다시 읽지 않으며 Scene·Driver·Renderer는 selector query나 getBoundingClientRect()
를 직접 호출하지 않는다.
- layout이 dirty일 때 한 application frame에서 read phase를 한 번 실행한 뒤
update와 render를 수행한다.
- runtime 생성 Surface만 dispose하며 Host가 제공한 SSR Surface는 제거하지 않는다.
- canvas·fallback·Inspector selector는 MISE HTML·TS·SCSS가 함께 소유한다.
- Host는 문구와
--mise-*custom property만 확장한다. - DOM query는 create/activate 경계에서 수행하고 frame hot path에서 반복하지 않는다.
- listener는 ResourceOwner에 등록한다.
- GSAP context는 lifecycle Scope와 함께 revert한다.
- Lenis frame은 MISE scheduler 하나에 연결한다.
- Barba hook은 PageChanger 호출만 하고 Scene 내부를 직접 조작하지 않는다.
afterChange는PageChanger.mount()Promise를 반환해 navigation 완료가 page mount 완료보다 앞서지 않게 한다.- reduced-motion에서는 의미를 유지한 대체 경로를 제공한다.
- reduced-motion이 실행 중 켜지면 GSAP animation과 frame lease를 즉시 정리한다.
11. Health·Debug
healthProfile로 추가한 Host key는 Provider
boot({ health }: MiseBootContext)
에서 health.mark(key)
로 관측한다.
MiseHealthReporter는 expected key mark만 허용하며 내부 HealthCheck resolve,
report 변경과 Service Locator 기능을 제공하지 않는다.
- Health는 실제 협력 호출 지점만 mark한다.
- 중복 mark는 멱등이다.
- expected profile 밖 mark는 report를 변경하지 않으며 healthy callback은 정확히 한 번만 실행한다.
- core expected set은 compiled capability에서 생성한다.
application.container,application.factory,runtime.clock은 core composition에서 관측한다.scene.object-factory는 Scene definition이 Object factory를 선언한 경우에만 expected set에 포함한다.page.motion은 실제 호출 시 관측하되 모든 Page의 기본 expected key로 강제하지 않는다.- 제품 profile은 framework core에 하드코딩하지 않는다.
- Debug Adapter 미등록은 internal Null Object로 대체하고 다른 optional Adapter의 부재처럼 compile 실패로 취급하지 않는다.
- Inspector는 상태를 읽지만 commit·resolve·dispose를 직접 실행하지 않는다.
- Playground는 별도 subpath와 lazy import를 사용한다.
- Debug factory는 shared
FrameControl을 주입받고 semantic control commit 뒤invalidate()만 요청한다. - lil-gui는 optional peer이며
autoPlace: false로 MISE container에 mount한다. - lil-gui
listen()은 별도 RAF를 생성하므로 금지하고 5Hz 이하 snapshot에서updateDisplay()만 호출한다. - Playground control은 scalar
get/set만 받고 Object3D·renderer·raw uniform을 받지 않는다. - Playground dispose는
GUI.destroy()와 container 제거를 모두 완료한다. - hot path log와 raw exception·stack·DOM·전체 URL을 금지한다.
- logger는 message와 context를 모두 sanitize한다. sanitizer·sink 실패가 application lifecycle을 중단하면 실패다.
- logger context는 collection 64개와 전체 node 256개를 상한으로 하며 credential,
개인정보, session, 전체 URL/query와 절대 경로를 Sink 전에 제거한다.
12. Package
- Host app과 publish package의
package.json·tsconfig·Vite config를 분리한다. - runtime dependency는 peer-only로 유지하고 UI component framework를 추가하지 않는다.
- required vendor는 peer, optional Adapter vendor는 optional peer로 둔다.
- 모든 peer는 library bundle에서 external 처리한다.
- export map은 허용 subpath를 열거한다.
- HTML·CSS·SCSS asset subpath를 각각 열거하고 root JS와 분리한다.
- Sass conditional export와 precompiled CSS를 동시에 제공한다.
sideEffects: false는 실제 import side effect 0을 검증한 뒤에만 사용한다.- tarball 자체를 publint, type resolver와 external fixture로 검사한다.
- 모든 code subpath는 하나의 API Extractor review entry와 committed API report로 drift를 검사한다.
- architecture graph는 module/edge 수가 0이면 실패하며 cycle·forbidden direction·unresolved relative import를 검사한다.
- architecture graph는 Container import/resolve 경계,
Contracts.tsfacade, production TS 파일 450줄 상한과 catch-all module 이름을 함께 검사한다. - Knip unused file·export·dependency 0과 Stryker mutation score 95 이상을 commercial Gate로 둔다.
- fixture는 HTML marker와 Sass
pkg:compile을 검사한다. - RC tarball을 실제 Host 제품에 재설치한 뒤 stable release한다.
13. Extension 판정
새 abstraction은 다음 네 조건을 모두 만족할 때만 추가한다.
- 실제 consumer가 둘 이상이거나 교체 가능한 boundary다.
- lifecycle owner가 명확하다.
- public API 증가보다 중복·결합 감소가 크다.
- 실패·dispose·type test를 작성할 수 있다.
하나라도 충족하지 않으면 제품 내부 concrete 구현으로 유지한다.