The Screen as a Resource — a Document the Server Hands Out, Not Code
5 minSo far the server answered with values. Now it answers with a screen.
The screen is a file
{ "type": "page", "title": "Desk",
"content": { "type": "center",
"child": { "type": "linear", "direction": "vertical", "spacing": 14, "alignment": "center", "children": [
{ "type": "text", "text": "WAITING", "style": { "fontSize": 16, "letterSpacing": 4, "color": "#6b7280" } },
{ "type": "text", "text": "{{waiting}}", "style": { "fontSize": 88, "fontWeight": "bold", "color": "#111827" } },
{ "type": "button", "label": "Admit one", "onTap": { "type": "tool", "tool": "desk.admit", "params": { "count": 1 } } } ] } } }Six lines. A value lands where {{waiting}} is, and the button calls the tool built in part 3.
The server hands it over
server.addResource(
uri: 'ui://desk',
name: 'Desk screen',
description: 'The desk screen, served as a document',
mimeType: 'application/json',
handler: (uri, params) async => ReadResourceResult(contents: [
ResourceContentInfo(
uri: 'ui://desk',
mimeType: 'application/json',
text: File(_screenPath).readAsStringSync(),
)
]),
);ResourcesCapability has to be on too.
capabilities: ServerCapabilities(
tools: ToolsCapability(listChanged: true),
resources: ResourcesCapability(listChanged: true),
),Reading it on every request is the point
readAsStringSync() sits inside the handler. Reading once at boot into a variable is obviously faster, and that is not what this does.
Hold it and the server has to restart every time the screen changes. At that moment the claim this piece makes — edit the file, get a different app — becomes false.
/// Read fresh on every request, deliberately. A screen cached at boot is a
/// screen you have to restart the server to change, and then the claim above
/// stops being true.When performance does become the problem, cache it — but invalidate on file change. Simply holding it is losing the feature.
What changes
Once the screen is outside the code:
| in the code | served as a document | |
|---|---|---|
| Change a font size | build → deploy → install | one line in a file |
| Kinds of client | an implementation each | all receive and draw the same document |
| Add a screen | modify the app | one more resource |
Verification — it fails if the screen is in the code
There is one place where this piece most easily becomes a lie: putting the screen in the code and writing that the server hands it out. The response alone cannot tell them apart.
ask step4 '{"jsonrpc":"2.0","id":2,"method":"resources/read","params":{"uri":"ui://desk"}}'
grep -q 'ui://desk' captures/s4.txt || die "resource not served"
grep -q 'page' captures/s4.txt || die "served document is not a screen"
# The claim is that the screen is not in the code.
grep -q 'fontSize' bin/step4.dart && die "step4: the screen is inside the code"One occurrence of fontSize in the server code and it fails.
step4 ui://desk served from ui/desk.json (6 lines), not from the codeRun it yourself
dart run bin/step4.dart
bash verify.shChange fontSize in ui/desk.json from 88 to 120 and ask again — the changed document comes back. The server was never stopped.
What to take
addResourceplus the capability — both, or it does not appear- Read inside the handler — cache it and it stops being install-free
- A no-screen-in-code check — the only place this claim could go false
Next
The waiting count is a variable. It dies with the process. Make it survive.
Run the sample
cd course-server dart pub get dart run bin/step4.dart