UI Runtime v3와 Morit Script
새 플러그인의 기본 UI Runtime은 v3이며 manifest에 ui_schema: 3을 명시합니다. 기존 ui_schema: 2와 Runtime v3 1.8 플러그인은 같은 방식으로 계속 실행됩니다. 새 slot, component event, picker 세부 props, retry를 사용하는 플러그인은 min_morit_version: "1.9.0"을 사용합니다.
패키지 구조
manifest.json
logic/school.morit
logic/school.ast.json
fixtures/day.json
manifest.json의 UI extension은 initial_state, computed_state, logic_modules, components, data_sources, view를 분리합니다. SDK가 .morit 소스를 한 번 컴파일해 AST, source map, source hash를 만들고 패키지에 함께 넣습니다. Host와 서버는 그 결과만 검증·실행하며 패키지 안의 소스를 다시 해석하지 않습니다.
{
"ui_schema": 3,
"min_morit_version": "1.9.0",
"logic_modules": [{
"id": "school",
"path": "logic/school.morit",
"exports": {
"normalizeDay": {
"input_schema": {"type": "object"},
"output_schema": {"type": "object"}
}
}
}]
}
표현식과 모듈
JSON의 {{= expression }}는 순수 표현식 하나만 담습니다. UTF-8 256바이트, AST 64노드까지이며 선언, 반복문, 함수 정의, callback은 넣을 수 없습니다. 같은 식이 반복되면 computed_state나 모듈 export로 옮깁니다.
반복·조건·함수·map·filter·reduce는 logic/*.morit에서 작성합니다. 문자열, 배열, 객체, 불변 ISO 날짜, date, html.toText, html.lines, html.decodeEntities를 사용할 수 있습니다. eval, 동적 import, prototype, 재귀, while, 네트워크·파일·환경변수·난수 접근은 지원하지 않습니다.
날짜 연산은 date.addDays(isoDate, days), date.weekday(isoDate), date.iso(isoDate)를 사용합니다. addDays는 YYYY-MM-DD, weekday는 일요일 0부터 토요일 6까지를 반환하므로 휴일 화면에서 다음 평일 범위를 계산할 수 있습니다.
평가 cycle 안에서 runtime.now, runtime.today, runtime.timezone, runtime.locale은 고정됩니다. runtime.screen은 width, height, orientation, size_class, text_scale, platform을 제공합니다. CLI와 preview는 고정 clock과 화면 조건을 주입할 수 있습니다.
UI와 데이터
repeat는 items, as, key를 갖고 중첩될 수 있습니다. component는 typed props와 key별 local state를 갖습니다. if, data_state, skeleton, animated, date_field, time_field, dialog, sheet를 사용할 수 있습니다. 날짜와 시간 입력은 Host의 Material picker를 사용합니다.
모든 node는 id, type, props, children, action, visible_when, slots만 사용합니다. columns, gap, wrap, grow, basis, width, min_width, max_width에는 {compact, medium, expanded} 값을 줄 수 있습니다. 이 목록은 SDK contract의 ui_runtime_v3에서 CLI, validator, Plugin MCP에 동일하게 공개됩니다.
공통 props: enabled, semantic_label, tooltip, padding, margin, width, height, min_width, max_width, min_height, max_height, background_color, foreground_color, border_color, border_width, border_radius, elevation, opacity.
반응형 값 지원 props: columns, gap, wrap, grow, basis, padding, margin, alignment, width, min_width, max_width, height, min_height, max_height.
row/flex/grid의 responsive 값은 compact(<600px), medium(600–839px), expanded(>=840px)로 지정합니다. row는 좁은 화면에서 자동으로 wrap하고, 텍스트·카드·목록은 viewport 제약 안에서 크기를 계산합니다. 한 repeat의 초기 표시량은 200개이며 다음 페이지로 이동합니다.
원본 JSON binding은 안전한 경로만 허용합니다: $, .field, ['field'], [index], [*]. wildcard 결과는 배열이고 JSONPath filter나 recursive descent는 없습니다. 필터링은 Morit Script transform에서 수행합니다.
{
"id": "school_day",
"capability": "school_fixture",
"trigger": "load",
"depends_on": ["selected_date"],
"when": "{{= state.selected_date != null }}",
"arguments": {"date": "{{= state.selected_date }}"},
"result_mode": "raw",
"transform": {"module": "school", "export": "normalizeDay"},
"refresh": {"on": ["load", "dependency_change", "resume", "manual"]},
"cache": {"policy": "stale_while_revalidate", "ttl_seconds": 300},
"retry": {"max_attempts": 2, "delay_ms": 500, "on": ["timeout", "network", "server"]}
}
Data source는 의존성 DAG 순서로 실행됩니다. 화면당 최대 8개를 선언할 수 있고, 동시에 실행되는
요청은 4개까지이며 나머지는 대기열에서 기다립니다. source당 1개 요청만 실행하며 dependency가 바뀐 이전 세대 결과는 폐기합니다. transform pipeline은 최대 8단계입니다. refresh.on에 load가 없으면 초기 실행하지 않고 when이 false가 되면 해당 data/error/busy 상태를 비웁니다. retry 기본값은 없음이며 validation, permission, Script 오류는 재시도하지 않습니다. none, memory, stale_while_revalidate 캐시만 허용합니다.
Response UI, 하단 영역, action
point: "response"는 v3에서도 사용할 수 있습니다. v2와 똑같이config.a2ui(v0.9 metadata와 schema)와 실행 capability를 가리키는 data source가 필요합니다. Host가 AI 답변에서 선택된 결과를 해당 source에 주입하므로 화면이 처음 표시될 때 같은 capability를 다시 호출하지 않습니다.bottom_bar는view와 같은 수준에 v3 node 하나로 선언합니다.action은surface,card,button,chip,form,field,select,switch에만 붙일 수 있습니다. 다른 node에 붙이면morit plugin validate가 거부합니다. component 내부에서는emit으로 이벤트를 올리고,flow안에서도emit과scope: "root"를 쓸 수 있습니다.- 첫 로딩·빈 데이터·오류 표시는
data_state와skeleton으로 구성합니다. 오류가 나면 Host 공통 “다시 시도” 안내도 표시되며 실패한 source만 다시 요청합니다.
검증과 preview
morit plugin validate ./school-life-runtime-v3
morit plugin runtime-test ./school-life-runtime-v3 \
--fixture fixtures/day.json \
--now 2026-09-15T16:00:00Z --timezone Asia/Seoul --json
morit plugin preview ./school-life-runtime-v3 --output dist/preview.html
Remote Plugin MCP의 morit_project_runtime_test는 mode: "fixture" | "live", capability, arguments, screen, now, timezone, locale을 공개합니다. 전체 source DAG를 실행한 뒤 Inspector 응답에 redacted raw data, transform 단계, computed state, request timeline, component/repeat 상태와 resolved UI tree를 함께 반환합니다. mode: "live"는 설정된 HTTPS Host와 설치된 GET 전용 http_json capability를 통해서만 읽습니다.
morit plugin validate와 Cloud 배포 검증은 Runtime v3 data source/action 인자가 capability input_schema와 일치하는지 확인합니다. 빌드된 manifest.json이 Host 설치 한도인 256KiB를 넘는 패키지도 배포 전에 거부합니다.
Remote MCP preview는 semantic preview이며 Android Host와 같다고 표시하지 않습니다. Local MCP의 target: "android"는 연결된 Host에서 설치된 플러그인을 열고, 사용할 수 없으면 fallback 이유와 semantic preview를 반환합니다.
Morit 1.9.0+109 Host에서 v3.1 기능을 광고하며 v3 publishing은 기본 활성화되어 있습니다. 운영자가 긴급 rollback할 때만 MORIT_PLUGIN_UI_V3_PUBLISH_ENABLED=false를 설정합니다.
학교생활 fixture는 공급자 API나 공용 키 없이 날짜별 시간표와 meals[].menu[]를 재현합니다. 교시 숫자, HTML 정리, 빈 날짜, 오류·retry, 날짜 dependency refresh, picker, 중첩 repeat, reusable component, 반응형 grid를 release 회귀 기준으로 사용합니다.
제한값
패키지는 모듈 16개, 모듈당 32KiB, 전체 소스 128KiB까지입니다. 모듈 AST는 2,048노드, 호출 깊이 24, 반복 입력 1,000개까지입니다. inline은 10,000 step·2MiB·16ms, UI/computed는 50,000 step·8MiB·100ms, 서버 transform은 100,000 step·16MiB·250ms입니다. timeout과 초과는 구조화된 Runtime 오류입니다.
v2에서 이동하기
기존 v2 UI JSON은 그대로 둡니다. v3가 필요할 때 ui_schema를 3으로 설정하고 SDK로 다시 build합니다. 반복되는 template은 computed/module로 추출하고, raw API 응답은 result_mode: "raw"로 받은 뒤 typed transform을 연결합니다. v2로 자동 변환하거나 데이터를 migration하지 않습니다.