画面はファイルだ — 四行から始める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バイト。白い板なら五千バイトを割る。
レンダー
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