Say Only That It Changed — Why the Value Must Not Ride Along
5 minThrough part 5, a screen wanting the latest value had to ask — every second, or when a person refreshed.
Tell it
One line after changing the value.
waiting -= n;
_save(waiting);
// Say that it changed. Not what it changed to.
server.notifyResourceUpdated('desk://waiting');Add a subscribable resource and turn subscribe: true on.
resources: ResourcesCapability(listChanged: true, subscribe: true),What goes on the wire
{"jsonrpc":"2.0","method":"notifications/resources/updated","params":{"uri":"desk://waiting"}}Only the uri. How many are waiting is not in it.
Why the value must not ride along
Putting the value in looks obvious — one round trip instead of two. But notifications guarantee no ordering.
server: waiting becomes 2 → notification A sent
server: waiting becomes 1 → notification B sent
client: B arrives (1) → A arrives (2) ← the order flipped
screen: 2With a value attached, a late old notification overwrites a newer value. The screen quietly shows the wrong number and nobody knows.
Send only "it changed" and the problem disappears. However many arrive, in whatever order, the receiver reads and gets the latest at that moment.
The kitchen screen piece reached the same conclusion. There it was a design choice; here the package only sends it that way — notifyResourceUpdated(uri) has no slot for a value.
Nothing goes out without a subscription
Caught while building. notifyResourceUpdated was called, the server logged it, and nothing appeared on the wire.
notified desk://waiting ← the server did call it
(nothing on the client side)Because it is not sent to clients that did not subscribe. That is the spec.
{"jsonrpc":"2.0","id":2,"method":"resources/subscribe","params":{"uri":"desk://waiting"}}After that it arrives. An easy place to mistake for a server defect — the server was right, the receiver had not asked.
Verification — parse it to see whether a value rode along
ask step6 \
'{"jsonrpc":"2.0","id":2,"method":"resources/subscribe","params":{"uri":"desk://waiting"}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"desk.admit","arguments":{"count":1}}}'params = json.loads(lines[0]).get('params', {})
extra = set(params) - {'uri'}
if extra:
print('the notification carried', sorted(extra), '— it must carry only the uri')
sys.exit(1)The first attempt checked with grep waiting and produced a false positive. The uri itself is desk://waiting, so it always matches. It cannot be a string check; the keys have to be parsed.
notification params: {"uri": "desk://waiting"}
step6 notification carried the uri and no valueSix steps done
step1 initialize -> serverInfo Course
step2 tools/list -> desk.count
step3 refused 0 and 99, admitted 1 -> waiting 2
step4 ui://desk served from ui/desk.json (6 lines), not from the code
step5 admitted 2, process ended, a new process still reads waiting 1
step6 notification carried the uri and no value
6 steps · each checked against its own claimOne server grew through six steps, and each step checks what that piece claims — not that it ran.
Run it yourself
cd content/sample/course-server
dart pub get
bash verify.sh # all six steps
dart run bin/step6.dart # the finished oneWhat to take
notifyResourceUpdated(uri)— having no slot for a value is the design- Subscribe first — nothing goes out without it
- Parse, do not grep — a string check trips on the uri
Next track
That is the server. Next is the client side — connecting, asking what it can do, and receiving a screen to draw.
Run the sample
cd course-server dart pub get dart run bin/step6.dart