openui 0.0.1-dev.2
openui: ^0.0.1-dev.2 copied to clipboard
Flutter Renderer widget for OpenUI Lang. Owns the streaming parser, reactive store, error boundary, and form-state cache.
openui #
Flutter Renderer widget for OpenUI Lang.
The renderer takes a streaming response: String from your LLM, parses it
against a Library<Widget> of components, and rebuilds the widget tree on
every chunk. It owns the reactive store, the form-state cache (so
TextEditingControllers survive mid-stream rebuilds), the query manager,
and a streaming-tolerant error boundary.
Status #
v0.1, Phase 2 complete. The package now ships:
Renderer— the streaming widget.ErrorBoundary— per-element error capture with last-good fallback.FormStateCache—TextEditingControllercache keyed by(formName, fieldName)with a 250 ms grace window.QueryManager—@Run-invalidatable query cache backed by aToolProvideror a testQueryLoader.RendererScope—InheritedWidgetexposing the store and form-state cache to component implementations.
Phase 3 ships the built-in component library on top of these primitives.
Install #
dependencies:
openui: ^0.1.0
openui_core: ^0.1.0
Quick start #
import 'package:flutter/material.dart';
import 'package:openui/openui.dart';
import 'package:openui_core/openui_core.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Renderer(
response: 'root = Text(text: "Hello, OpenUI!")',
library: Library<Widget>(<Component<Widget>>[
defineComponent<Widget>(
name: 'Text',
schema: Schema.fromMap(const <String, Object?>{
'type': 'object',
'properties': <String, Object?>{
'text': <String, Object?>{'type': 'string'},
},
}),
render: (ctx, props, renderNode, id) =>
Text(props['text'] as String? ?? ''),
),
]),
),
),
);
}
}
Renderer is a StatefulWidget; each call with a new response runs the
streaming parser, seeds the reactive store, and fires any queries the
parsed program declares.
Renderer API #
| Field | Purpose |
|---|---|
response: String? |
Cumulative source from the LLM. null = no content yet. |
library: Library<Widget> |
Component registry. Required. |
isStreaming: bool |
Whether response is still appending. Propagated to components via RendererScope.isStreaming. |
onAction: void Function(ActionEvent)? |
Notified when an action plan fires. |
onStateUpdate: void Function(Map<String, Object?>)? |
Notified after every store write with the post-write snapshot. |
initialState: Map<String, Object?>? |
Persisted state seed. Keys include the leading $. Wins over parsed defaults. |
onParseResult: void Function(ParseResult)? |
Fired once per parse pass. |
toolProvider: ToolProvider? |
Production transport for Query / Mutation. |
queryLoader: QueryLoader? |
Test seam — bypasses toolProvider. |
onError: void Function(List<OpenUIError>)? |
Notified when the active error set changes. |
rootName: String |
Entry-point statement (default 'root'). |
How components consume the renderer #
Each component defines a ComponentRender<Widget> callback:
typedef ComponentRender<W> = W Function(
EvalContext context,
Map<String, Object?> props,
W Function(AstNode node, EvalContext context) renderNode,
String statementId,
);
The renderer pre-resolves prop values for you:
- Primitive props — already evaluated (e.g.
text: "hi"arrives as"hi"). - Reactive props (
x-reactive: truein the schema, bound to a$stateref) — arrive as aReactiveAssignmarker. Readmarker.valueand write user edits back tomarker.targetviacontext.store.set(target, ...). - Child components / arrays of components — pre-rendered to
Widget/List<Widget>so you can drop them straight into your tree. - Action props (
@Set,@Reset,@Run,@ToAssistant) — arrive asvoid Function()callbacks that the renderer ties to its action dispatcher.
Need access to the form-state cache or the store from a component?
RendererScope.maybeFind(context) exposes both.
License #
MIT — see LICENSE.