Constructs
Construct declarations, construct sections, content blocks, lifecycle hooks, and construct-defined declaration forms.
Constructs let the compiler model library-defined declaration shapes.
Defining a Construct
annotation State {
}
annotation Binding {
}
annotation Env {
}
construct Widget {
annotations {
@State;
@Binding;
@Env;
}
requires {
content;
}
lifecycle {
onAppear() {}
onDisappear() {}
onChange(of: value) {}
}
builder {
content;
}
representation {
children: [Widget];
}
}The frontend recognizes these section families today:
annotationsmodifiersrequireslifecyclebuilderrepresentation- custom sections preserved structurally
The annotations { ... } section is an allowlist for the construct domain. Each listed annotation must be declared with annotation Name { ... } or provided by an imported module. The annotation declaration owns the parameter schema; the construct owns whether that annotation is meaningful for the construct form.
Construct Forms
A construct can introduce its own declaration form:
Widget Dashboard(title: String, theme: ThemeConfig) {
@State
let selectedTab: Int = 0;
content {
Kit.Column("Operations") {
Kit.Text("Operations")
}
}
onAppear() {
return;
}
}This is real compiler behavior today, and nested builder calls now retain their child-builder structure in the semantic model instead of being discarded after parsing.
Content Requirements
Construct rules can require a content { ... } block. When a form omits required content, the semantics layer raises KSEM022 ("missing required content block").
Lifecycle Validation
Constructs also declare which lifecycle hooks are valid. If a form uses an undeclared hook, the semantics layer raises KSEM021 ("invalid lifecycle hook").
Why Constructs Matter
Constructs are one of the clearest examples of Kira's intended boundary:
- the compiler provides mechanisms and validation
- higher-level library behavior still belongs in Kira source
- annotations are declared as source symbols, while constructs decide their domain-specific meaning
Callback Blocks Versus Builder Blocks
Kira deliberately keeps two different mental models:
- ordinary APIs such as
app.onFrame { frame in ... }use callback blocks and lower as function values - construct-driven DSL calls such as
Column() { Text("hello") }keep builder/tree structure so libraries can inspect children semantically
That distinction is what lets callback-style lifecycle code stay executable while UI-style constructs keep real child trees for layout, theming, and diffing.