숫자는 파일에 없다 — 같은 파일, 두 그림

5 분
목표화면 강좌 3편. `{{now}}` 하나로 화면과 값이 갈라진다. 그리고 그게 진짜 갈라졌는지는 한 파일을 서로 다른 상태로 두 번 렌더해서 확인한다.

2편까지의 화면은 파일에 적힌 것만 보여준다. 대기번호를 그렇게 만들면 번호가 바뀔 때마다 화면 파일을 새로 보내야 한다.

자리를 비워 둔다

{ "type": "page", "title": "Step 3",
  "initialState": { "now": "0", "waiting": 0 },
  "content": { "type": "center",
    "child": { "type": "linear", "direction": "vertical", "spacing": 10, "alignment": "center",
      "children": [
        { "type": "text", "text": "NOW SERVING", "style": { "fontSize": 15, "letterSpacing": 5, "color": "#6b7280" } },
        { "type": "text", "text": "{{now}}", "style": { "fontSize": 92, "fontWeight": "bold", "color": "#111827" } },
        { "type": "text", "text": "{{waiting}} waiting", "style": { "fontSize": 20, "color": "#6b7280" } } ] } } }

두 가지가 늘었다.

initialState — 이 화면이 시작할 때의 값. 열자마자 빈 칸이 보이지 않게 하는 용도다.

{{now}} — 그 자리에 상태의 now 를 넣으라는 표시. 통째로 써도 되고, {{waiting}} waiting 처럼 문장 안에 섞어도 된다.

값을 넣는다

rt.stateManager.set('now', '42') 처럼 키와 값을 넘기면, 그 키를 쓰는 노드가 다시 그려진다. 호스트는 어떤 노드였는지 모르고, 알 필요도 없다.

클라이언트 강좌에서 만든 것과 붙이면 하나씩 넣을 일도 없다.

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);
},

서버 강좌 3편에서 도구가 {"now": ..., "waiting": ...} 를 돌려주도록 만들어 뒀다. 그 맵의 키와 화면의 {{...}} 이름이 같으면 사이에 변환 코드가 없다.

바인딩이 진짜인지 확인하는 법

{{now}} 를 쓰고 화면에 42가 뜨면 됐다고 생각하기 쉽다. 그런데 파일에 42 라고 적어도 화면에는 42가 뜬다.

그래서 검사는 두 갈래다.

# 값이 파일에 박혀 있으면 실패
grep -qE '"(42|7)"' screens/step3.json && die "step3: the value is baked in"
grep -q '{{now}}' screens/step3.json || die "step3: nothing is bound"

# 한 파일, 두 상태. 그림이 안 움직이면 그 바인딩은 장식이다.
cmp -s captures/step3.png captures/step3_alt.png && \
  die "step3: the same file rendered the same picture for two different states"

하니스가 step3.json 을 두 번 렌더한다. 한 번은 now: 42, waiting: 3, 한 번은 now: 7, waiting: 0. 같은 파일이다.

step3      587 B, 8 lines, type "page" -> step3.png
step3_alt  587 B, 8 lines, type "page" -> step3_alt.png
step3  one file, two states, two pictures (27577 B vs 24734 B)

두 PNG 가 다르다. 파일이 하나이므로, 다르게 만든 건 상태다.

검사가 실제로 잡는지 확인했다

전부 통과하는 검사는 아무것도 증명하지 않는다. 그래서 {{now}} 를 42 로 바꿔서 한 번 돌려봤다.

   [4/8] step3 — the number is not in the file
   step3: the value is baked in

걸린다. 같은 방식으로 4편과 5편의 검사도 한 번씩 깨뜨려서 확인했고, 셋 다 걸렸다.

어디까지 상태로 둘 것인가

NOW SERVING 은 고정 글자다. {{label}} 로 빼서 상태로 만들 수도 있었다.

빼지 않았다. 상태로 만들면 그 값을 누군가 매번 채워 줘야 한다. 안 채우면 빈 화면이 나온다. 안 바뀌는 글자는 파일에 적는 게 실패할 자리가 하나 적다.

기준: 값을 정하는 쪽이 화면 바깥에 있으면 상태, 아니면 파일.

직접 돌려보기

cd content/sample/course-ui
bash verify.sh
open captures/step3.png captures/step3_alt.png

가져갈 것

  1. initialState + {{key}} — 자리를 비우고 이름을 준다
  2. stateManager.set — 호스트는 키와 값만 안다
  3. 두 상태 두 캡처 — 그림이 움직여야 바인딩이다

다음 편

값이 들어왔다. 이제 나가는 쪽 — 버튼을 눌렀을 때 무슨 일이 일어나는가.

샘플 실행하기

makemind-academy/course_ui/
git clone https://github.com/makemind-academy/course_ui
cd course_ui
bash verify.sh
GitHub에서 열기 →