Language Guide

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:

  • annotations
  • modifiers
  • requires
  • lifecycle
  • builder
  • representation
  • 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.

On this page