화면은 파일이다 — 네 줄로 시작하는 UI 런타임
5 분클라이언트 강좌 4편에서 화면이 도착했다. ui://desk 를 읽으면 546바이트가 왔고, 클라이언트는 그걸 그냥 문자열로 받아서 길이만 세고 끝냈다.
이번 트랙은 그 문자열이 그림이 되는 쪽이다.
네 줄
{ "type": "page", "title": "Step 1",
"content": { "type": "center",
"child": { "type": "text", "text": "The whole screen is this file",
"style": { "fontSize": 34, "fontWeight": "bold", "color": "#111827" } } } }233바이트. 이게 화면 전부다. 노드가 셋 있다.
page— 한 화면. 제목을 가진다.center— 자식 하나를 가운데에 둔다.text— 글자.
트리다. page 안에 content 가 하나, center 안에 child 가 하나. 여러 개를 담는 노드는 2편에서 나온다.
이걸 그리는 쪽
final rt = MCPUIRuntime(enableDebugMode: false);
// screen = jsonDecode(파일 내용)
await tester.runAsync(() => rt.initialize(screen));
await tester.pumpWidget(RepaintBoundary(
key: key,
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Builder(
builder: (c) => rt.buildUI(
context: c,
onToolCall: (tool, params) async => fired.add(tool),
),
),
),
));initialize 에 맵을 넘기고, buildUI 가 위젯을 돌려준다. 그 사이에 화면에 대한 판단은 없다.
이 호스트가 테스트 안에서 도는 건 캡처를 뜨기 위해서다. 앱이라면 pumpWidget 자리에 runApp 이 온다 — 위아래 두 줄은 그대로다.
onToolCall 은 4편에서 쓴다. 지금은 이름을 받아 적기만 한다.
무엇이 없는지가 요점이다
이 트랙의 호스트는 host/test/capture_test.dart 파일 하나다. 다섯 편이 끝날 때까지 이 파일은 거의 안 바뀐다 — 다섯 화면 전부 같은 호스트가 그린다.
그래서 이 강좌에서 가장 중요한 검사는 "있는지"가 아니라 "없는지" 다.
# 화면에 보이는 글자가 프로그램 안에 있으면 실패한다.
grep -rq 'The whole screen is this file' $SRC && \
die "step1: the words on the screen are inside the host"
# 위젯을 손으로 짓고 있으면 실패한다.
grep -rqE '\bText\(|\bColumn\(|\bRow\(|\bElevatedButton\(' $SRC && \
die "step1: the host builds widgets by hand"$SRC 는 host/test host/pubspec.yaml 이다. host/build 는 뺐다 — 컴파일 산출물에 문자열이 그대로 박혀 있어서, 거기까지 훑으면 이 검사가 영원히 통과한다. 실제로 4편 검사를 처음 붙였을 때 그 이유로 잘못 걸렸다.
빈 그림도 그림이다
캡처는 RepaintBoundary.toImage 로 뜬다. 화면이 아예 안 만들어져도 PNG 는 나온다 — 하얀 PNG 가.
그래서 파일이 있는지만 보면 안 된다.
for s in step1 step2 step3 step3_alt step4 step5_empty step5_filled; do
[ -s "captures/$s.png" ] || die "$s: no capture"
# 만들어지지 않은 화면도 PNG 는 남긴다 — 비어 있는 PNG 를.
[ "$(wc -c <captures/$s.png)" -gt 5000 ] || die "$s: the capture is blank"
donestep1.png 는 25,196바이트다. 하얀 판이면 5천 바이트 아래로 떨어진다.
렌더
step1 233 B, 4 lines, type "page" -> step1.png1000×640 논리 픽셀, 2배 배율. 연보라 배경 한가운데에 굵은 검은 글자.
이 문장은 capture_test.dart 어디에도 없다.
직접 돌려보기
cd content/sample/course-ui
bash verify.sh
open captures/step1.png가져갈 것
page→content→child— 화면은 트리다initialize+buildUI— 호스트가 하는 일은 이 둘뿐이다- 없음을 검사한다 — 화면의 내용이 프로그램에 없다는 걸 확인해야 "화면이 파일이다"가 사실이 된다
다음 편
노드 하나로는 화면이 안 된다. 여러 개를 어디에 놓을지 — 좌표 없이.
샘플 실행하기
makemind-academy/course_ui/git clone https://github.com/makemind-academy/course_ui cd course_ui bash verify.sh