ボタンは名前を呼ぶだけ — 押したときホストがすること

5 分
目標画面講座 第4回。onTap にコードは無く、ツール名しかない。その名前が本当にホストまで届いたかは絵では確かめられないので、ハーネスが押して書き取る。

第3回で値が画面に入ってきた。逆向きが残っている。

onTap にコードが無い

{ "type": "button", "label": "Call next", "onTap": { "type": "tool", "tool": "queue.next" } },
{ "type": "button", "label": "Take a ticket", "onTap": { "type": "tool", "tool": "queue.take" } }

onTap は { type, tool } だ。「このボタンを押すと queue.next というツールを呼ぶ」までがすべてで、そのツールが何をするかは書いていない。

書けもしない。このファイルはサーバー講座で作ったサーバーが送ってきたもので、実行はそのサーバーで起きる。

受ける側

rt.buildUI(
  context: c,
  // 名前がここに来る。ホストはこの名前が何なのか知らない。
  onToolCall: (tool, params) async => fired.add(tool),
)

第1回で空の関数にしておいた場所だ。実アプリなら、ここにクライアント講座3回目の一行が入る。

onToolCall: (tool, params) async {
  // 名前は画面から来た。そのまま渡す。
  final r = await client.callTool(tool, params);
  final state = jsonDecode((r.content.first as TextContent).text)
      as Map<String, dynamic>;
  state.forEach(rt.stateManager.set);
},

tool をそのまま渡す。if (tool == 'queue.next') のような分岐は無い — 入れた瞬間、サーバーがツールを一つ増やすたびにホストを直すことになる。

絵では確かめられない

ここで初めてキャプチャが足りなくなる。step4.png にはボタンが二つ見える。だがそのボタンが 押されたとき何を送るか は見えない。

onTap を丸ごと消しても絵は同じだ。

だからハーネスが押す。

fired.clear();
await tester.tap(find.text('Call next'));
await tester.pump(const Duration(milliseconds: 40));
await tester.tap(find.text('Take a ticket'));
await tester.pump(const Duration(milliseconds: 40));
say('pressed -> the host was asked for: ${fired.join(", ")}');
pressed -> the host was asked for: queue.next, queue.take

fired は onToolCall が受け取った名前を順に入れたリストだ。この行が run.log に残り、検査がそれを読む。

# ホストがツール名を知っていれば失敗
grep -rq 'queue\.next\|queue\.take' $SRC && \
  die "step4: the host knows the tool names"
# 押したとき、画面が求めた名前が来なければ失敗
grep -q 'the host was asked for: queue.next, queue.take' $LOG || \
  die "step4: pressing did not send the tool names the screen asked for"

二行は揃って初めて意味になる。上だけなら「ホストに名前が無い」までで、下だけなら名前がホストに埋まっていても通る。重ねて初めて 画面が渡した名前がそのまま出て行った になる。

onTap を消して回すと下の行が捕まる。

   [5/8] step4 — the button names a tool and stops there
   step4: pressing did not send the tool names the screen asked for

失敗は画面で処理しない

queue.next が断られたらどうなるか。サーバー講座 第3回のカウンターは、待っている人がいなければ呼ぶのを断る。

断りも結果だ。サーバーが isError と理由を返し、それが他の値と一緒に状態として入ってくる。

{ "type": "text", "text": "{{notice}}", "style": { "fontSize": 15, "color": "#9ca3af" } }

step4.json の下のほうにあるこの一行がその場所だ。画面定義にエラー処理の文法は別に無い — 理由も状態の一値でしかない。

レンダー

step4  896 B, 12 lines, type "page" -> step4.png

43、その下に 2 waiting、ボタン二つ、その下に灰色で called 43。

自分で回してみる

cd content/sample/course-ui
bash verify.sh
cat captures/run.log

持ち帰るもの

  1. onTap: { type: 'tool', tool: ... } — 画面は名前までしか知らない
  2. onToolCall はそのまま渡す — 名前で分岐しない
  3. 押してみる検査 — キャプチャが見られないものはログに残す

次回

最後。画面一つが違う形になること — そして数を知らない一覧。

サンプルを実行する

makemind-academy/course_ui/
git clone https://github.com/makemind-academy/course_ui
cd course_ui
bash verify.sh
GitHub で開く →