Shapes
Define reusable shape styles under theme.shapes, then reference them with a
shapeStyleKey:
{
"theme": {
"shapes": {
"card": {
"kind": "roundRect",
"fillColor": "#173B63",
"cornerRadius": [20, 20, 36, 20],
"strokeColor": "#65C7F7",
"strokeWidth": 2,
"shadows": [
{
"x": 0,
"y": 8,
"blur": 20,
"spread": 0,
"color": "#00000066"
}
]
}
}
}
}
Shape({
width: 420,
height: 220,
shapeStyleKey: 'card',
})
The current shape kind is roundRect.
Shape fields
| Field | Type | Purpose |
|---|---|---|
kind | "roundRect" | Geometry kind. |
fillColor | color string | Interior color. |
cornerRadius | number, "pill", or four numbers | All corners or [topLeft, topRight, bottomRight, bottomLeft]. |
strokeColor | color string | Optional outline color. |
strokeWidth | number | Outline width. |
shadows | array | Outer or inset shadow definitions. |
shapeStyleKey | string | Inherit another shape style before applying local fields. |
fallbackUri | string | Optional fallback asset used by shape resolution. |
width, height | number | Reference dimensions required for some Roku preprocessing. |
mask | boolean | Opt the style into use as a generated mask source. |
A shadow contains x, y, blur, color, and optional spread and inset.
Inherit and override a shape
{
"theme": {
"shapes": {
"card": {
"kind": "roundRect",
"fillColor": "#173B63",
"cornerRadius": 20
},
"focusedCard": {
"shapeStyleKey": "card",
"fillColor": "#245B91",
"strokeColor": "#FFFFFF",
"strokeWidth": 3
}
}
}
}
The referenced style is resolved first; local fields override it.
Platform compilation
BaseApp.loadShapes() runs during launch and stores the resolved forms under
theme.compiledShapes.
- Web and native-drawing platforms keep the resolved shape fields and render them with the platform shape node.
- Roku sends shape requests to the
imageGeneratortask, writes generated nine-patch images tocachefs:/hosanna-shapes, and renders them as posters.
This is a runtime launch step. The current source does not support per-shape
delivery values or an hst app-config:compile shape-delivery workflow.
On Roku, a "pill" radius needs positive width and height in the theme
entry; without them, preprocessing warns and produces square corners. A style
with mask: true also needs positive reference dimensions so its mask image can
be generated.
Shapes inside fragments
Fragment trees can contain hs_shape and MaskGroup children. Give each
child a stable ID so status overlays and the provider can address it:
{
"$supportsDataMap": true,
"views": {
"base": [
{
"id": "surface",
"subType": "hs_shape",
"shapeStyleKey": "card",
"width": 384,
"height": 216
},
{
"id": "mask",
"subType": "MaskGroup",
"shapeStyle": "card",
"maskSize": [384, 216],
"children": [
{
"id": "poster",
"subType": "Poster",
"uri": "${data.imageUrl}",
"width": 384,
"height": 216
}
]
}
],
"normal": {
"surface": { "shapeStyleKey": "card" }
},
"focused": {
"surface": { "shapeStyleKey": "focusedCard" }
}
}
}
shapeStyleKey selects the style for an hs_shape node. shapeStyle selects
the geometry used by a MaskGroup; a maskUri can remain as a bitmap
fallback.
The fragment provider handles these fields differently by platform:
- On Roku it turns
hs_shapeinto compiled poster/nine-patch fields, resolvesMaskGroup.shapeStyleto the compiled mask image, and scalesmaskSizeto output-resolution pixels. - On web, Apple, and Android it leaves the shape metadata and design-point mask size for the platform bridge to render.
- A status overlay that changes
shapeStyleKeyis translated to the corresponding compiled Roku poster fields.
Load shapes before acquiring fragment instances. If a fragment was hydrated before its shapes compiled, the provider marks that pooled instance stale and rebuilds it on a later acquisition.
See Fragment Callbacks and Lifecycle for pooled cleanup rules and Fragment Performance and Debugging for shape diagnostics.
Reloading config
Replacing AppConfig clears its computed theme.compiledShapes branch. Call
loadShapes() again after a config reload, and recreate fragments that were
hydrated before the new shapes finished loading.