Learning outcome
Implement a real red-to-green policy change for independent checklist items.
Work on your learning branch. The goal is to reject duplicate checkbox data paths within one generated surface while still allowing the same path name in separate surfaces. Component IDs and data paths have different roles, so the existing duplicate-ID guard cannot enforce this policy. Write the failure before changing validation.
Append to server/protocol.test.mjs — expected to fail first
test('rejects two checklist items sharing a data path', () => {
const reply = { text: 'Prepare', components: [
{ id: 'root', component: 'Column', children: ['a', 'b'] },
{ id: 'a', component: 'CheckBox', label: 'Pack charger', value: { path: '/ready0' } },
{ id: 'b', component: 'CheckBox', label: 'Plan travel', value: { path: '/ready0' } },
] };
assert.throws(() => check(reply), /Duplicate checkbox path/);
});
test('allows independent checklist paths', () => {
const reply = { text: 'Prepare', components: [
{ id: 'root', component: 'Column', children: ['a', 'b'] },
{ id: 'a', component: 'CheckBox', label: 'Pack charger', value: { path: '/ready0' } },
{ id: 'b', component: 'CheckBox', label: 'Plan travel', value: { path: '/ready1' } },
] };
assert.deepEqual(check(reply).messages[1].updateDataModel.value,
{ ready0: false, ready1: false });
});
Run from a2ui/server
npm test
The first test should fail with a missing expected exception on the pinned baseline; the second should pass. If both pass before your implementation, confirm your checkout and test discovery. The existing check helper supplies an event registry and surface ID; neither fixture requires an external feed or Gemini call.
Solution: insert inside validateSurface
// Beside `const ids = new Set();`, before the component loop:
const checkboxPaths = new Set();
// Inside `for (const c of reply.components)`, immediately AFTER
// the existing CheckBox binding-format validation:
if (c.component === 'CheckBox') {
if (checkboxPaths.has(c.value.path)) {
throw Error('Duplicate checkbox path');
}
checkboxPaths.add(c.value.path);
}
Place the new set inside validateSurface, not at module scope: each surface needs an independent namespace. Place the policy after binding-format validation so malformed bindings fail before you dereference them. Rerun npm test: all 12 tests should now pass. The output check belongs on the server; adding a prompt sentence alone cannot enforce it.
Make the policy teachable
This sample chooses independent tasks. Shared bindings can be intentional in another application, so document this as PocketCommunity policy rather than an A2UI specification requirement. The extension belongs in your learner branch; the pinned production source is unchanged.
Practice and checkpoint
Keep the failing output, implementation diff and passing output in your learning log. Add a third test calling validateSurface twice with /ready0 to prove the set is scoped per request. Explain why changing component IDs would not fix shared binding state.
Source and next steps
- Pinned implementation — The exact app and server revision used by this lesson.
Back to roadmap · Practice this unit in the codelab