# Widget round trip demo: source

> The code that makes the Widget round trip demo work, walked through snippet by snippet.

The agent shows a grind picker in the chat. A tap goes back to the agent as a turn, and the agent answers by changing the product page.

## A button that talks back

The widget is a document, not code. Its buttons send a message to the agent when tapped.

```json file=demos/kettle/widgets/kettle-grind-picker.json
{
  "type": "Button",
  "props": {
    …
    "children": "Filter, medium"
  },
  "on": {
    "click": {
      "sendToAgent": "Grind: Filter"
    }
  }
},
```

## The prompt says what a tap means

The tap arrives as plain text, and a policy rule turns it into action.

```md file=demos/kettle/prompt/policy-rules.md
2. When a message is "Grind: <choice>", or the visitor names a grind or a brewer, call set_grind with the matching grind AND function__kettle-brew-guide with that method, in ONE response. A V60, Chemex or pour over is Filter; a moka pot or machine is Espresso; a cafetiere or plunger is French press.
```

## The page answers

`set_grind` is a page tool. It changes the product page and tells the agent what it did.

```ts file=src/demos/kettle/pageTools.ts
execute: (args) => {
  …
  store.dispatch({ type: 'set-grind', grind, by: 'agent' })
  return `The product page now shows ${grind} (${GRIND_SIZE[grind]}) as the grind.`
},
```

## The recipe card

The brew guide function returns a recipe, and the page draws its card straight from that result.

```tsx file=src/demos/kettle/index.tsx
if (e.type !== 'tool_end' || !isBrewTool(e.toolName) || e.isError || e.refused) return
const card = brewOf(e.output)
if (card) setBrew(card)
```
