Hosanna Fundamentals
Hosanna screens and components are TypeScript classes whose state is reconciled
into platform-rendered views. The essential contract is @view + BaseView +
@state + getViews().
A Minimal View
The following shape matches current views in hosanna-ui-samples-public:
// The generated file exists after `npm run generate` or `npx hst generate:all`.
import {
WelcomeCard,
WelcomeCardViewStruct,
} from '@hs-generated/app/WelcomeCard-generated-struct';
import type {
WelcomeCardState,
} from '@hs-generated/app/WelcomeCard-generated-struct';
export { WelcomeCard, WelcomeCardViewStruct };
import { state, view } from '@hs-src/hosanna-ui/lib/decorators';
import { BaseView } from '@hs-src/hosanna-ui/views/lib/BaseView';
import {
ViewState,
ViewStruct,
} from '@hs-src/hosanna-ui/views/lib/view-api';
import { Button } from '@hs-src/hosanna-ui/views/controls/Button';
import { VGroup } from '@hs-src/hosanna-ui/views/groups/VGroup';
import { Label } from '@hs-src/hosanna-ui/views/primitives/Label';
@view('WelcomeCard')
export class WelcomeCardView extends BaseView<WelcomeCardState> {
@state title = 'Welcome';
@state activationCount = 0;
protected override getViews(): ViewStruct<ViewState>[] {
return [
VGroup([
Label({ text: this.title }),
Button({ text: 'Continue' })
.isInitialFocus()
.onClick(() => {
this.activationCount++;
this.title = `Activated ${this.activationCount} times`;
}),
]).itemSpacing(24),
];
}
}
The generated import path depends on the source file's path beneath the
generator root. Do not copy the illustrative @hs-generated/app/... path
without adapting it to your project.
What Each Part Does
@view('WelcomeCard')registers the class name used by generation and reflection.BaseView<WelcomeCardState>supplies lifecycle, reconciliation, focus, layout, child lookup, and renderer ownership.@statecreates a reactive field. Assigning it schedules the view to be reconciled.getViews()returns an array ofViewStructdescriptions. It does not directly create DOM, UIKit, Android, or SceneGraph elements.WelcomeCard({...})is the generated factory other views use to include this component.
State and Inputs
Generated state fields serve two roles:
- They are inputs when a parent calls
WelcomeCard({ title: 'Browse' }). - They are reactive fields when the view assigns
this.title = 'Playing'.
Do not add a constructor merely to receive component props. Use @state fields
and the generated factory. Keep transient collaborators, timers, and other
non-render data as normal class fields.
Rendering Rules
- Keep
getViews()deterministic for the current state. - Do not assign state, start requests, or perform navigation inside
getViews(). - Use arrow functions for event handlers that need
this. - Give interactive or later-accessed views stable IDs.
- Use the concrete view class—not its factory function—as a lookup generic,
such as
getSubView<LabelView>('title'). - Clean up timers, observations, or native resources in the appropriate lifecycle hook.
Generate and Validate
Generation is part of normal development:
npm run generate
npm test
In the sample repository, npm test, npm run build, and hst run perform
generation as part of their workflow. If a direct TypeScript or Vitest command
cannot resolve @hs-generated, run the repository's generation script first.
Continue with Views, SubViews, and Children and Core and Native Components.