How it renders across platforms
The contract is the same. Only the presentation changes — the server has no say in it.
| Component | iPhone | iPad | Mac |
|---|---|---|---|
| page | stack + grouped list | list-detail, 2 columns | list-detail, 3 columns |
| section | section | section | section |
| row | row / navigation | same | same + hover |
| metric | row with a large value | grid possible | grid possible |
| table | list + lazy loading | table: columns, sorting | table + selection + menu |
| field | form controls | same | grouped form |
| button | button with a role | same | same + keyboard shortcut |
| chart | chart | same | same |
| toolbar | action bar | action bar | bar + app menu |
What to keep in mind when authoring the contract
- A table on iPhone shows as a list: the first two columns matter, the rest collapse — put the important one on the left.
- metric tiles — wrap several in a
sectionwithcolumns: 2. - Swipes (
swipe_actions) exist only on iOS; on Mac they become a context menu — don’t make them the only way to perform an action. - More than 5 tabs won’t fit on iPhone — keep it to five.
Navigation and services (outside the contract)
- The app opens straight into the active service.
- The tab bar is shown only if the manifest has
tabs: the service’s tabs + a fixed “Settings” tab. Withouttabs— a singleroot_page. - Switching and adding services, and tokens, live in the client’s “Settings” screen.
- Push: the payload carries
service_id+page_endpoint→ tapping makes the service active and opens the page.