Worked Examples
Recommended integration architecture
Non-normative, but strongly recommended and followed by the sample apps: keep the design system pure, and give Milano exactly one doorway into it.
- Design system (
DesignSystem/,designsystem/): plain UI components taking typed models (a banner model with layout, heights, and alignment; a button model with a label, an enabled flag, and a tap closure). Zero Milano imports: every component is previewable and unit-testable on its own, and reusable outside Milano entirely. - Milano bridge (
MilanoBridge/,milanobridge/): the only UI-layer code that imports Milano. For each component, an initializer that builds the model from a MilanoNode (properties read once, events wired into the model’s closures), and the renderer that registers it. One obvious file per component; the whole Milano surface of the design system is auditable at a glance. - Environment: engine setup, builders, the action funnel, the observer. Screens depend on this service, never on engine internals.
The result: Milano never leaks into the components, the components never learn about documents, and replacing either side leaves the other untouched.
The two v0.1 target capabilities, written end to end: a shared vocabulary, two documents, and the behavior each must produce. Both live as executable vectors in conformance/, in the format of the conformance suite spec. Together they exercise every v0.1 mechanic: context, expressions, state, events, built-in and custom actions, consent gating, and async completion.
The vocabulary
A minimal consumer vocabulary, examples 1.0.0, with seven component types and two custom actions:
| Component | Properties | Events | Children |
|---|---|---|---|
Banner | backgroundImageUrl: string | yes | |
Column | yes | ||
Text | text: string | ||
Button | label: string, enabled: bool | tap (no payload) | |
TextField | label: string, value: string | change: string | |
NumberField | label: string, value: double | change: double | |
Checkbox | label: string, checked: bool | change: bool |
| Action | Declared | Parameters |
|---|---|---|
openUrl | Vocabulary (global) | url: string |
submitContact | Form document (local) | name: string, surname: string, email: string, phone: string? |
What these components look like is entirely the consumer’s business: Milano only guarantees their properties arrive resolved and typed, and their declared events dispatch.
The artifact lives once at conformance/examples/vocabulary.json, beside its vectors per the suite layout: nothing references it by path, mirroring the runtime, where a document never names its vocabulary. It validates against the official meta-schema schemas/vocabulary.schema.json.
The banner
A background image, a personalized title, a subtitle, and a call-to-action button that asks the host to open a URL.
{
"version": "0.1.0",
"context": { "userName": "string" },
"root": {
"type": "Banner",
"id": "banner",
"properties": { "backgroundImageUrl": "https://cdn.example.com/promo.jpg" },
"children": [
{
"type": "Text",
"id": "title",
"properties": { "text": { "$expr": "concat('Hello, ', context.userName)" } }
},
{
"type": "Text",
"id": "subtitle",
"properties": { "text": "A summer of savings starts today." }
},
{
"type": "Button",
"id": "cta",
"properties": { "label": "Learn more", "enabled": true },
"on": {
"tap": [ { "action": "openUrl", "url": "https://example.com/promo" } ]
}
}
]
}
}
What it demonstrates:
- Structure without data: the document declares that it reads
context.userName; the host injects the value ("Ada"), and the title resolves toHello, Ada. The same cached document greets every user. - The action boundary: tapping the button dispatches
openUrlwith its capturedurlparameter to the host’s action handler. Milano does not open URLs; the host does. - Vector:
conformance/banner-open-url.jsonexpects the resolved tree, the dispatched action, and zero occurrences.
The contact form
Name, surname, email, optional phone, and a consent checkbox that gates submission. All field composition is document-defined: adding a field is a document change, not an app release.
{
"version": "0.1.0",
"state": {
"name": "string",
"surname": "string",
"email": "string",
"phone": "string?",
"consent": "bool",
"submitted": "bool"
},
"actions": {
"submitContact": {
"parameters": {
"name": "string",
"surname": "string",
"email": "string",
"phone": "string?"
}
}
},
"root": {
"type": "Column",
"id": "form",
"children": [
{
"type": "TextField",
"id": "name",
"properties": { "label": "Name", "value": { "$expr": "state.name" } },
"on": { "change": [ { "action": "$set", "key": "name", "value": { "$expr": "event" } } ] }
},
{
"type": "TextField",
"id": "surname",
"properties": { "label": "Surname", "value": { "$expr": "state.surname" } },
"on": { "change": [ { "action": "$set", "key": "surname", "value": { "$expr": "event" } } ] }
},
{
"type": "TextField",
"id": "email",
"properties": { "label": "Email", "value": { "$expr": "state.email" } },
"on": { "change": [ { "action": "$set", "key": "email", "value": { "$expr": "event" } } ] }
},
{
"type": "TextField",
"id": "phone",
"properties": { "label": "Phone (optional)", "value": { "$expr": "state.phone ?? ''" } },
"on": { "change": [ { "action": "$set", "key": "phone", "value": { "$expr": "event" } } ] }
},
{
"type": "Checkbox",
"id": "consent",
"properties": { "label": "I agree to be contacted", "checked": { "$expr": "state.consent" } },
"on": { "change": [ { "action": "$set", "key": "consent", "value": { "$expr": "event" } } ] }
},
{
"type": "Button",
"id": "submit",
"properties": {
"label": "Send",
"enabled": { "$expr": "state.consent && !isEmpty(trim(state.name)) && !isEmpty(trim(state.surname)) && !isEmpty(trim(state.email)) && !state.submitted" }
},
"on": {
"tap": [
{
"action": "$when",
"condition": { "$expr": "state.consent && !isEmpty(trim(state.name)) && !isEmpty(trim(state.surname)) && !isEmpty(trim(state.email)) && !state.submitted" },
"then": [
{
"action": "submitContact",
"name": { "$expr": "trim(state.name)" },
"surname": { "$expr": "trim(state.surname)" },
"email": { "$expr": "trim(state.email)" },
"phone": { "$expr": "state.phone" },
"onSuccess": [ { "action": "$set", "key": "submitted", "value": true } ]
}
]
}
]
}
},
{
"type": "Text",
"id": "thanks",
"properties": { "text": { "$expr": "if(state.submitted, 'Thanks! We will be in touch.', '')" } }
}
]
}
}
What it demonstrates:
- State shape in the document, values from outside: the state data provider supplies the initial values (empty strings,
nullphone,falseflags), awaited during the asynchronous build. - The unidirectional loop: each field renders
state.xand itschangeevent runs$setwith the event payload. The renderer never writes state; the loop closes through dispatch. - A document-local action:
submitContactis declared by the form document itself, not by the app’s bundled vocabulary. When the producer adds a field, the document changes its declaration and its dispatch together; no app release involved. (openUrl, a generic capability, stays global.) - Consent gating, twice: the
enabledexpression handles presentation (the consumer’s Button decides what disabled looks like), and the$whenguard handles semantics: atapemitted while the condition is false dispatches nothing. The gate is in the document, not in renderer logic. - Async completion:
submitContactis delivered to the host’s handler; the sequence does not wait. When the handler succeeds,onSuccesssetssubmitted, which disables the button and reveals the thank-you text throughif(...). - Optionality:
phoneisstring?end to end:nullfrom the provider, displayed via?? '', passed asnullto the handler when never edited. - Vector:
conformance/form-contact-consent.jsonwalks the full interaction: fill fields, tap while unconsented (nothing dispatches), consent, tap, complete with success; expects the final state store, the single dispatched action, and zero occurrences.