rune 1.21.0
rune: ^1.21.0 copied to clipboard
Converts Dart widget code strings into real Flutter widgets at runtime via AST interpretation.
Changelog #
All notable changes to this project are documented here. Format follows Keep a Changelog; versioning follows Semantic Versioning.
1.21.0 - 2026-04-23 - borders, gradients, time breadth #
Added #
-
Border.all(color?, width?, style?, strokeAlign?)value builder. All four named args are optional and default to Flutter's own values (Colors.black,1.0,BorderStyle.solid,strokeAlignInside).widthaccepts anynumand coerces todouble. -
Border.symmetric(vertical?, horizontal?)value builder. Both arms optional, defaulting toBorderSide.none. Use when the left/right pair should match each other and the top/bottom pair should match each other. -
Standalone
BorderSide(color?, width?, style?, strokeAlign?)value builder for composing a single side (commonly used withOutlineInputBorder/UnderlineInputBorderor passed toBorder.symmetric). -
LinearGradient(colors, begin?, end?, stops?, tileMode?)value builder.colorsis required.begin/endaccept anyAlignmentGeometry; defaultAlignment.centerLefttoAlignment.centerRight.stopsis an optionalList<num>coerced toList<double>. -
RadialGradient(colors, center?, radius?, stops?, tileMode?)value builder. Defaults:Alignment.center,radius: 0.5. -
SweepGradient(colors, center?, startAngle?, endAngle?, stops?, tileMode?)value builder. Defaults to a full-circle sweep (0to2 * pi) fromAlignment.center. -
BoxDecorationnow forwardsborder:andgradient:. Previously those two named args were silently dropped so the new builders can finally compose:BoxDecoration( border: Border.all(color: Colors.red, width: 2), gradient: LinearGradient(colors: [Colors.blue, Colors.green]), ) -
BorderStyle.none/BorderStyle.solidandTileMode.clamp/.repeated/.mirror/.decalconstants now register as part of the default constant set. -
Durationruntime properties.duration.inDays,.inHours,.inMinutes,.inSeconds,.inMilliseconds, and.inMicrosecondsresolve directly from source. Useful for"$elapsed.inSeconds seconds"-shaped formatters. -
DateTimeruntime properties..year,.month,.day,.hour,.minute,.second,.millisecond,.microsecond,.weekday,.millisecondsSinceEpoch. Combined with the new methods, source can express non-trivial date arithmetic without host-side helpers. -
DateTimeruntime methods..isBefore(other),.isAfter(other),.difference(other)(returnsDuration),.add(duration),.subtract(duration)(returnDateTime). -
README gains a "Support me with a cup of coffee" section above
Licenselinking tobuymeacoffee.com/canarslandev.
Notes #
- 38 new tests; total root test count 1798 to 1836, analyzer clean.
1.20.0 - 2026-04-21 - geometry constructors, color breadth, state-management trio #
Added #
Color.fromARGB(alpha, red, green, blue)value builder. Four positional ints build an opaque or partially-transparent color without the0xAARRGGBBhex dance. Closes a deferral noted incolor_builder.dartsince Phase 2b.Color.fromRGBO(red, green, blue, opacity)value builder. Three ints plus anumopacity (0.0-1.0, int-coerced to double) give the opacity-channel shape most tutorials use.Colorruntime properties.color.alpha,.red,.green,.blue,.opacity,.valueare now resolvable from source. Enables expressions likeColor.fromARGB(accent.alpha, 255 - accent.red, 0, 0)without bouncing through host Dart.Radius.circular(x)andRadius.elliptical(x, y)value builders. Compose non-uniform corner radii withBorderRadius.only(topLeft: Radius.circular(12), ...).- Four new
BorderRadiusnamed-constructor builders.BorderRadius.all(radius),.only(topLeft, topRight, bottomLeft, bottomRight),.vertical(top, bottom),.horizontal(left, right). Complements the existing.circular(n)shortcut. Omitted arms on.only/.vertical/.horizontaldefault toRadius.zero, matching Flutter. Positioned.fill(child)value builder. Stretches the given child to every edge of its parent [Stack]. Override individual sides withleft:/top:/right:/bottom:; omitted sides default to0. Closes the deferral noted inpositioned_builder.dart.rune_routerv0.2.0: source-level navigation via ImperativeRegistry.RouterBridge(router: myRouter)now registers sixRouter.*prefixed imperatives onconfig.imperatives:Router.go,Router.push,Router.pop,Router.pushReplacement,Router.goNamed,Router.pushNamed. Source can callonPressed: () => Router.go('/settings')directly without bouncing navigation through a hostonEventcallback. The plainconst RouterBridge()form is unchanged (only widget + value builders registered). Four new tests cover registration + Router.go + Router.push / Router.pop round-trip through a liveGoRouter; 24 package tests pass.rune_providerv0.2.0: MemberRegistry integration.Consumer.builderandSelector.selectornow pass the raw notifier to the closure when the notifier does NOT implementRuneReactiveNotifier. Paired withrunev1.17+'sMemberRegistry, consumers register property / method accessors on their notifier type and Rune source can dot-access them directly without implementingRuneReactiveNotifieror projecting astateMap. ExistingRuneReactiveNotifiernotifiers keep working unchanged (thestateMap is still extracted for them). Two new integration tests cover the new path; 21 package tests pass.rune_testsibling package (v0.1.0). Two things in one package: test-ergonomics helpers (pumpRuneView,expectRuneRenders) that wrap the 10-lineMaterialApp + Scaffold + RuneViewharness most widget tests write by hand, plus arune_formatcommand-line executable (dart run rune_test:rune_format <file> [--write | --check | --line-length N]) wrappingformatRuneSource. The--checkflag enables CI jobs that fail when a source file has drifted from canonical format, in the same shape asdart format --set-exit-if-changed. 6 widget tests cover the helpers.rune_lintsibling package (v0.1.0). Test-time validation helpers.validateRuneSource(tester, source, config, {data})pumps aRuneViewinside aWidgetTesterand returns aList<RuneLintIssue>describing every exception that surfaced during the first render;expectValidRuneSource(tester, source, config)is a thin matcher wrapper thatfails with a readable listing. Thekindfield lets consumer tests filter categories (parseError,unregistered,invalidArgument,missingBinding,resolveError). 9 widget tests cover every category. Depends onflutter_testat runtime because the validator takes aWidgetTester.rune_riverpodsibling package (v0.1.0). RegistersProviderScope+RiverpodConsumeron aRuneConfigthroughRiverpodBridge. Providers are passed in viadata:; the consumerbuilder(ctx, value, child)receives the provider's current value (or theRuneReactiveValue.toRuneMap()projection if the value implements that interface). Depends onflutter_riverpod ^2.5.0. Completes the state-management trio alongsiderune_providerandrune_bloc. 12 unit + widget tests cover bridge registration, argument validation, and end-to-endStateProvider<int>+StateProvider<CounterState>flows throughRuneView.
1.19.0 - 2026-04-21 - DevTools Phase 3 host side (source hot-edit) #
Added #
RuneInspectorgains source-override support.setSourceOverrideById(int id, String? source)(plus a handle-typedsetSourceOverride) installs or clears a source-string override for a liveRuneView. The boundRuneViewreparses and rebuilds on the next frame, surfacing the override in place of its declared source.- Two new VM service extensions registered alongside
ext.rune.inspecton the first view mount:ext.rune.edit(id: string, source: string): apply override.ext.rune.reset(id: string): clear override. Both return{"ok": true, "id": N}on success; error responses useinvalidParamsfor shape errors andextensionErrorfor missing ids.
- Payload additions. Every inspection entry now carries
overridden: boolandoriginalSource: string | null(the pre-override source; only populated while an override is active). Enables DevTools UIs to display a "reset to original" affordance. RuneInspector.registerOverrideListener(handle, listener)soRuneViewcan wire itssetStateinto the inspector without exposing internals. The listener fires on every override change and is scrubbed automatically onunregisterView.- 4 new tests covering the end-to-end hot-edit flow through a
mounted
RuneView: override replaces rendered source, payloadoverridden/originalSourcefields, null revert, and override-revert-override cycle.
Notes #
- Release builds pay zero cost (
dart:developer.registerExtensionis compiled out). Host code that calls the override API in release still works but has no effect because no DevTools client can reach the running process. - Pairs with
rune_devtools_extension ^0.2.0, which ships the matching Edit / Reset UI on top of the new endpoints.
[Unreleased-older] #
Added #
rune_blocsibling package (v0.1.0). RegistersBlocProvider,BlocBuilder, andBlocListeneron aRuneConfigthroughBlocBridge. Pairsflutter_bloc ^8.1.0with aRuneReactiveStateinterface that state classes implement (Map<String, Object?> toRuneMap()) so Rune source can dot-access individual fields. Dual ofrune_provider'sRuneReactiveNotifierpattern; teams pick whichever state-management framework they already use. 16 unit + widget tests cover the bridge registration, per-widget argument validation, and end-to-end cubit-emit flows throughRuneView.rune_httpsibling package (v0.1.0).RuneHttpView(url, config, data, cacheDuration, ...)fetches Rune source from an HTTP endpoint, caches it in memory (process-wideInMemoryRuneSourceCache, keyed by URL, default TTL 5 min), and renders through an innerRuneView. Stale entries are served instantly while a background refresh runs; failed fetches fall back to the cached copy. PluggableRuneSourceCache/RuneSourceFetcherinterfaces let hosts wire persistent caches (shared_prefs / Hive / SQLite) or auth-header fetchers. 16 tests cover freshness math, cache round-trip, fetcher success/failure mapping, and end-to-end widget flows including Retry and cache-wins-when-fetch-fails. Unlocks the server-driven-UI narrative this package was designed for.rune_devtools_extensionsibling package (v0.1.0). Phase 2 of the DevTools plan. Ships a Flutter-web DevTools extension underpackages/rune_devtools_extension/that registers a rune tab inside Flutter DevTools. Calls theext.rune.inspectendpoint registered by v1.18.0 and renders one expandable card per liveRuneViewsurfacing source, data context, cache size, and last error. 7 unit tests on the wire-format parser. CI workflow extended with a sixthanalyze + testblock covering the new package.- Root README gains a row for
rune_devtools_extensionin the Bridge-packages table. - Hosted
runedependency on siblings. Each sibling pubspec declaresrune: ^1.18.0independencies:(resolvable on pub.dev) and keeps the monorepopath: ../..as adependency_overridesentry (only applied during local development). Fixes the publish blocker where pub.dev rejectedpath:dependencies. guides/folder for extended documentation. Six focused markdown guides complement the root README:getting-started.md,source-syntax.md,cookbook.md,bridges.md,devtools.md,troubleshooting.md, plus an indexguides/README.md. README now links them from a "Detailed documentation" section near the top;CONTRIBUTING.mddocuments the rule that README andguides/must stay in sync on every user-facing change.- Monorepo publish pipeline.
tool/publish_all.shnow encodes the release order across the six packages. The script builds the DevTools extension bundle, publishesrune_devtools_extensionwhile the bundle is on disk, then removes the bundle before publishing the other siblings and finally the mainrunepackage. Working around adart pub publishquirk: the tool walks the full subtree of the invoked directory and does not understand nested packages; a leftover 30 MB compiled bundle inpackages/rune_devtools_extension/extension/devtools/build/would otherwise sweep into the mainrunearchive. With the script the main archive is ~374 KB compressed, the sibling archives range from 4-20 KB each, andrune_devtools_extension(which MUST ship the bundle for DevTools to render the tab) stays at 11 MB. - Flutter-web scaffold + build pipeline for
rune_devtools_extension. The package gains aweb/source folder (tracked in git), atool/build_bundle.shconvenience script, and a distinct.pubignoreso the compiled bundle underextension/devtools/build/lands on pub.dev without ever entering git history. Consumers installing from pub.dev get the bundle automatically; path-dependency consumers runtool/build_bundle.shonce after cloning. Git tracks only the Dart source + the ~20 KB web scaffold, keeping repo size unchanged; pub.dev archives the ~30 MB compiled bundle because Flutter DevTools cannot fetch CanvasKit from its local-scheme iframe.
Removed #
- GitHub Pages dartdoc hosting. The
.github/workflows/docs.yamlworkflow, root README API-docs badge + hosted-docs reference, and every package'sdocumentation:pubspec field pointing athttps://canarslandev.github.io/rune/are all gone. pub.dev already rendersdart docoutput on every published release, so the hand-rolled Pages deployment was redundant. Consumers follow the pub.dev-provided API-docs link from each package's landing page instead.
1.18.0 - 2026-04-20 - DevTools inspection (Phase 1) #
Added #
-
RuneInspectorsingleton inlib/src/binding/rune_inspector.dartthat tracks liveRuneViewinstances for DevTools introspection. Every_RuneViewState.initStatecallsRuneInspector.instance.registerView(snapshotBuilder)and stores the returned handle;disposeunregisters. Snapshot builders are invoked on demand so DevTools always sees the freshest state. -
ext.rune.inspectVM service extension. The inspector lazily registers this endpoint viadart:developer.registerExtensionon the first view mount. DevTools (or any VM-service client) calls it and receives:{ "views": [ { "id": 0, "source": "Text('hi')", "data": {"count": 7}, "cacheSize": 1, "lastError": null } ] }One entry per live view; numeric
ids match theRuneInspectorHandle.idthe host received. Release builds short-circuit to a no-op becausedart:developer.registerExtensionis compiled out. -
Robust JSON coercion. Snapshot values pass through
_serialiseForWirewhich recursively mapsMap/Iterablebranches and coerces non-JSON-native leaves (RegExp,DateTime, arbitrary objects) to theirtoString()form so the wire payload always round-trips throughjsonEncode. -
Error isolation. A misbehaving snapshot builder that throws is caught and its error is surfaced on the affected view's entry as
snapshotError: "<exception>.toString()"; other views in the same payload stay intact. -
Barrel exports
RuneInspector,RuneInspectorHandle, andRuneInspectionSnapshotBuilderfrompackage:rune/rune.dartso a companion DevTools extension package can drive the registry without reaching intosrc/.
Notes #
- Phase 1 of the
rune_devtools_extensionwork perdocs/superpowers/plans/2026-04-20-rune-v1.16-devtools-extension-plan.md. Phase 2 ships the sibling package scaffold; Phase 3 ships the Flutter web UI that consumes this payload. _RuneViewStategained two fields (_inspectorHandle,_lastError) and two lifecycle hooks (inspector registration ininitState, deregistration indispose;lastErrorcaptured in the rendercatchblock). The runtime render path pays a single Map allocation per inspection call; zero cost per render.- 13 new tests: 8 unit tests on
RuneInspectorcovering register/unregister, duplicate-unregister tolerance, snapshot freshness, error isolation, JSON round-tripping, non-native-leaf coercion; plus 5 integration smokes wiring through a realRuneView(mount registers, unmount deregisters, two mounted views produce two distinct entries,lastErrorsurfaces after a render throws, payload JSON-encodes end-to-end). Total main-package tests: 1751 (up from 1738).
Changed #
- Benchmark harness (
benchmark/parse_resolve_bench.dart) gains a second workload. Alongside the historical 30-node canonical tree, a "rich" source now exercises v1.x features (for/ifelements, string interpolation, deep dot-paths, runtime properties and methods, ternary expressions). Both workloads print COLD (cache-miss) and WARM (cache-hit) stats plus the multiplier of headroom against the 16ms 60fps budget. - README gains a Performance section with the v1.17.1 numbers captured on an Apple-Silicon dev machine (COLD p95 around 410us-450us; WARM p95 around 50us-121us; ~36x-39x headroom).
1.17.1 - 2026-04-20 - source formatter polish #
Changed #
formatRuneSourcehandles map literals properly. Previously the formatter fell through to analyzer's owntoSource()forSetOrMapLiteralnodes, which emits'a' : 1with a space before the colon. A dedicated_writeSetOrMapLiteralhelper now renders map entries as'a': 1(idiomatic style) and applies the same fits-vs-break logic the list-literal path already uses: short maps stay single-line, long maps break onto one entry per line with trailing commas.- Empty
{}still renders as{}(Dart's default-Set interpretation is preserved because the formatter does not synthesise a type annotation).
Added #
- 11 new formatter tests covering map literal short + long forms, set literals, list with if-elements, list with for-elements, string-interpolation preservation, ternary, arrow closure preservation, empty argument lists, and deeply-nested-call progressive indentation. Total formatter tests: 23 (up from 12).
- Total main-package tests: 1738 (up from 1728).
1.17.0 - 2026-04-20 - user-registered runtime members #
Added #
-
MemberRegistryonRuneConfig. Hosts and sibling bridges can now register property accessors and method invokers for arbitrary Dart types without taking adart:mirrorsdependency or forking the main package. Typical shape:final config = RuneConfig.defaults(); config.members ..registerProperty<CounterNotifier>('count', (t, _) => t.count) ..registerMethod<CounterNotifier>('increment', (t, args, _) { t.increment(); return null; });After this, Rune source can write
counter.countandcounter.increment()directly against a liveCounterNotifierin the data context. -
Registry integration across three resolver arms.
PropertyResolverconsults the registry after the built-in property whitelist misses, before the Map-absent-key fallback.IdentifierResolver.resolvePrefixedconsults the registry fordata.membershapes where the data value is a non-Map holder (e.g. a bareCounterNotifierin data).InvocationResolver._dispatchRuntimeMethodconsults the registry forreceiver.method(...)calls when the receiver is NOT a recognized built-in target type, falling back to the built-in method whitelist otherwise.
-
Type-matching semantics.
registerProperty<T>(...)andregisterMethod<T>(...)useis Tat resolve time, so a subtype ofTalso matches. Registration order is the tiebreaker when multiple entries could fire: first-match-wins. -
Built-in safety. For stock Dart types (String, List, Map, num, ThemeData, ColorScheme, TextTheme, MediaQueryData, controller types, Animation, Animatable, AsyncSnapshot, BoxConstraints, Size, EdgeInsets, Route, RouteSettings) the built-in whitelist always wins. A host cannot accidentally shadow
String.toUpperCaseorList.contains. Custom classes never collide because they fall outside the built-in guard. -
Barrel exports
MemberRegistry,MemberPropertyAccessor, andMemberMethodInvokerfrompackage:rune/rune.dart.
Notes #
- Unblocks cleaner bridge patterns:
rune_providerv0.2.0 (planned) drops theRuneReactiveNotifier.stateMap-projection indirection once it migrates to registering direct property accessors. RuneContext.membersis nullable so pre-v1.17 callers that construct aRuneContextdirectly keep working; when null the three resolver arms skip the custom-member lookup entirely.- 15 new tests: 11 unit tests on
MemberRegistry(round-trip, subtype matching, first-match-wins, no-match fallback, method invocation, ChangeNotifier end-to-end) plus 4RuneViewintegration smokes (property on custom ChangeNotifier, method call with / without args, built-in-wins-on-stock-types). Total main-package tests: 1728 (up from 1713).
1.16.0 - 2026-04-20 - pluggable imperative registry #
Added #
ImperativeRegistryonRuneConfig. Hosts and sibling bridges can now register source-level imperatives without a main-package update:config.imperatives.registerBare(name, handler)wires up bare-shape calls (showToast('hi'),logEvent(name: 'tap')).config.imperatives.registerPrefixed(target, method, handler)wires up prefixed-shape calls (Router.go('/path'),Analytics.track('sign-up')). Target + method pair is the key.- Handler signature is
Object? Function(ResolvedArguments, RuneContext), mirroring widget/value builders so existing helpers (voidEventCallback, closure adapters) compose naturally.
- Registry-first dispatch in
InvocationResolver. The resolver consults the registry before falling back to the hardcoded v1.3+ built-ins (showDialog,showModalBottomSheet,Navigator.*, etc.). Hosts that want to shadow a built-in can do so by registering a same-named handler. Built-ins stay active for any name not registered on the instance. - Barrel exports
ImperativeRegistryandImperativeHandlerfrompackage:rune/rune.dart.
Notes #
- Unblocks
rune_routerv0.2.0 (planned):Router.go('/path')and friends can finally live in source. The v1.14.0 deferred-scope note pointing at "pluggable imperative registry in the mainrunepackage" is now addressed. RuneContext.imperativesis nullable so pre-v1.16 callers that construct aRuneContextdirectly (pure unit tests, older bridges) continue to work unchanged; when null the resolver falls back to the built-in bridges exclusively.- 12 new tests: 8 unit tests on
ImperativeRegistry(round-trip, duplicate detection, collisions) plus 4RuneViewintegration smokes (bare call, prefixed call, registered-shadows-built-in,Navigator.*still-wins-when-no-override). Total main-package tests: 1713 (up from 1701).
1.15.0 - 2026-04-20 - docs + example polish #
Added #
- Example app rewritten as a 4-tab showcase.
example/lib/main.dartgrows from 2 to 4RuneViews:- Cart and Profile tabs from v0.2.0+ remain unchanged.
- Reactive tab drives a
ChangeNotifiercounter throughrune_provider'sChangeNotifierProvider,Consumer, andSelector. DemonstratesRuneReactiveNotifier.stateprojection, rebuild suppression viaSelector, and host-side event dispatch throughonEvent. - Responsive tab uses
rune_responsive_sizer's.w/.h/.spextensions to build a viewport-relative layout. - Each tab owns its own
RuneConfig: default for Cart and Profile,withBridges([ProviderBridge()])for Reactive,withBridges([ResponsiveSizerBridge()])for Responsive.
- Root README grows a Cookbook section with copyable recipes
covering: two-way binding,
if-element conditional rendering, ternary event selection, reactive counters viarune_provider, percent-of-screen sizing viarune_responsive_sizer, and inline routing viarune_router. - Root README grows a Writing a bridge guide that walks
through scaffolding a new
RuneBridgepackage from scratch (pubspec, barrel, bridge class, widget / value / constant / extension registration) with live references to the four existing bridges ordered by complexity.
Changed #
- Root README "Testing" section updated to reflect the current
test matrix (1701 root + 146 sibling tests).
example/README.mdrewritten to document the 4-tab layout;example/pubspec.yamlnow path-depends onrune_providerandrune_responsive_sizerso the demo runs out of the box from a cleanpub get.
Notes #
- Main
runepackage (1.14.0 to 1.15.0) is a pure ecosystem / docs bump. No widget / value / constant / resolver changes at the main-package layer. Root test count unchanged at 1701.
1.14.0 - 2026-04-20 - routing bridge (rune_router) #
Added #
- rune_router sibling bridge package (v0.1.0). Third
third-party-style bridge. Registers a narrow surface of
package:go_routeron aRuneConfigthroughRouterBridge:GoRoute(path, builder, name?, routes?)value builder. Thebuilderclosure is(BuildContext, GoRouterState) -> Widget.GoRouter(routes, initialLocation?, debugLogDiagnostics?)value builder. Filters non-GoRoute entries fromroutes:so conditional[if (...)]list constructs compose cleanly.GoRouterApp(router, title?, theme?, debugShowCheckedModeBanner?)widget. WrapsMaterialApp.router(routerConfig:)so Rune source can install the router at the app root.
Notes #
- Main
runepackage (1.13.0 to 1.14.0) is a pure ecosystem bump. No widget / value / constant / resolver changes at the main-package layer. Main test count unchanged. - Source-level imperatives (
context.go('/path'),context.push('/path')) stay deferred. They need a pluggable imperative registry in the mainrunepackage. Host apps navigate by holding a reference to the router instance and calling.go(...)/.push(...)fromonEventcallbacks. - rune_router ships at
0.1.0, matching the cadence ofrune_cupertinoandrune_provider. 20 tests pass; analyzer clean undervery_good_analysis ^5.1.0.
1.13.0 - 2026-04-20 - reactive state bridge (rune_provider) #
Added #
- rune_provider sibling bridge package (v0.1.0). Second
third-party-style bridge; registers a curated trio of
package:providerwidgets on aRuneConfigthroughProviderBridge:ChangeNotifierProvider,Consumer, andSelector. Each widget works with aChangeNotifierscoped to a single provider; source interacts with the notifier's state through aMap-shapedRuneReactiveNotifier.stategetter so dot-access (state.count) resolves correctly through Rune's existing property resolver. RuneReactiveNotifierinterface in the new package. Implement alongsideChangeNotifierand overrideMap<String, Object?> get stateto expose typed fields to Rune source without touching Rune's built-in member whitelist.
Notes #
- Main
runepackage (1.12.0 to 1.13.0) is a pure ecosystem bump to match the release cadence. No widget / value / constant / resolver additions on the main package in this release. Feature substance lives entirely inpackages/rune_provider/. - rune_provider ships at its own version track (
0.1.0), mirroringrune_cupertino.rune_providertests: 19 passing. Package analyzes clean undervery_good_analysis ^5.1.0. rune_routerstays deferred; the next bridge to ship.
1.12.0 - 2026-04-20 - v1.x deferred cleanups #
Added #
- ListenableBuilder widget. Rebuilds on any
Listenablechange via abuilder(context) => Widgetclosure. Pair withValueNotifier,ChangeNotifier,AnimationController, or any composite listenable to scope rebuilds narrowly. - PageRouteBuilder value builder. Accepts
pageBuilder,transitionsBuilder, and the usualDuration/ barrier slots. Two new closure adapters (toPageRouteBuilderPageBuilder,toPageRouteBuilderTransitionsBuilder) bridge the 3- and 4-arity Flutter signatures intoRuneClosures. Works with the existingNavigator.pushimperative bridge. - Navigator.popUntil. Imperative bridge pops routes until a
user-supplied predicate returns
true. The predicate receives aRouteand can reachroute.settings.name,route.isFirst,route.isCurrent,route.isActivethrough the runtime's property whitelist. Closure adaptertoRoutePopPredicatewraps theRuneClosureintoRoutePredicate. - showMenu imperative. Same shape as
showDialog/showModalBottomSheet: takes aBuildContext,RelativeRectposition, and a list ofPopupMenuEntrys, returns the selected value through a future. - RelativeRect.fromLTRB value builder. Positional-constructor wrapper used for positioning popup menus.
- CheckedPopupMenuItem widget. Selectable popup entry with
leading checkmark. Complements the existing
PopupMenuItemandPopupMenuDividerpair. - BottomSheet widget. Inline bottom-sheet host (as opposed to
the
showModalBottomSheetimperative), wiringonClosing,backgroundColor,elevation,shape,clipBehavior, andenableDraginto Flutter'sBottomSheet. - FilledButton.tonal value builder. The Material 3 "tonal" filled
button variant. Same slot set as
FilledButton. - SnackBarAction + SnackBar.action slot.
SnackBarActionis a new value builder (label +onPressedevent name) and theSnackBarvalue builder now accepts anaction:slot. Required once actionable snackbars were promoted from examples to first-class. - PaginatedDataTable + RuneDataTableSource. Covers the paged
variant of
DataTableend-to-end: the value builder adapts aMap<String, Object?>backing store intoDataTableSourcerows through an event-name callback. - AnimationController.drive, Tween.animate, Tween.chain. Three dispatch arms added to the runtime method whitelist so Rune source can compose Animatables inline without escaping into the host app.
- Route / RouteSettings property whitelist.
Route.isFirst,Route.isActive,Route.isCurrent,Route.settings,RouteSettings.name,RouteSettings.argumentsare now readable from Rune source, unlocking predicates like(route) => route.settings.name == '/home'insidepopUntil.
Notes #
- PopupMenuDivider.thickness / indent / endIndent / color stay
deferred. The CI-pinned Flutter 3.24.0
PopupMenuDividerconstructor exposes onlykeyandheight; those extras were added in later Flutter framework releases. They unlock automatically when the CI Flutter floor moves. - Main-package test count climbs from 1619 to 1701 (+82 tests).
rune_responsive_sizer stays at 7 tests, rune_cupertino stays at
117 tests. All three analyzers clean under
very_good_analysis ^5.1.0.
1.11.0 - 2026-04-19 - bridge ecosystem kickoff #
Added #
- rune_cupertino sibling bridge package (v0.1.0). First
third-party-style bridge delivered as a separate package:
packages/rune_cupertino/. One class,CupertinoBridge, implementsRuneBridge.registerInto(config)and wires the Cupertino widget family, aCupertinoThemeDatavalue builder, and 30CupertinoIcons.*constants into a RuneConfig withconfig.withBridges([const CupertinoBridge()]). - Ten Cupertino widget builders.
CupertinoApp,CupertinoPageScaffold,CupertinoNavigationBar,CupertinoButton,CupertinoSwitch,CupertinoSlider,CupertinoTextField(with the same stateful-controller pattern as the Material TextField),CupertinoActivityIndicator,CupertinoAlertDialog,CupertinoDialogAction. Registered through the bridge contract so consumers opt in explicitly. - Event-callback helpers on the public barrel.
voidEventCallbackandvalueEventCallback<T>are now exported frompackage:rune/rune.dart. Bridge packages previously could not wire event-carrying widgets without reaching intopackage:rune/src/...paths; exporting these helpers aligns the bridge pattern with the "no src/ imports from external code" convention.
Notes #
- Main
runepackage (1.10.0 to 1.11.0) is a pure ecosystem bump: no widget / value / constant additions and no resolver changes beyond the barrel export. The feature substance of the release lives in the new sibling package. packages/rune_cupertino/ships at its own version track starting at0.1.0. 72 tests across the bridge registration, per-widget argument forwarding, event dispatch, disabled-state paths, value builder, constants seed, and six end-to-end integration smokes that mount aRuneViewinside aCupertinoApp.rune_provider(Provider / Riverpod integration) andrune_router(go_router / auto_route integration) stay deferred. Either they ship as patch releases to v1.11.x, or they land in a future v1.x release; the decision waits for real adoption feedback on pub.dev to drive the ordering.
Deferred from the plan #
- The v1.x roadmap anticipated three bridges shipping together in
v1.11.0. The actual delivery focused on
rune_cupertinobecause the Cupertino widget set is a self-contained, high-value package that demonstrates the bridge pattern end-to-end without bringing along additional third-party dependencies. CupertinoPicker,CupertinoActionSheet,CupertinoSegmentedControl, andCupertinoContextMenustay deferred inside rune_cupertino itself; each has complex shape (closure-heavy builders, generic typing, imperative dispatch) that fits a follow-up patch to rune_cupertino rather than the first release.
v1.x series close #
v1.11.0 closes the post-v1.0.0 v1.x roadmap:
| Release | Theme |
|---|---|
| v1.0.0 | Stability milestone |
| v1.1.0 | Lifecycle + controllers |
| v1.2.0 | Closure-based builder widgets |
| v1.3.0 | Dialogs + overlays + imperative bridges |
| v1.4.0 | Theme + Material 3 polish |
| v1.5.0 | Forms + validation |
| v1.6.0 | Navigation + routing |
| v1.7.0 | Gestures + advanced interaction |
| v1.8.0 | Data tables + expansion + stepper |
| v1.9.0 | Explicit animations |
| v1.10.0 | Developer experience |
| v1.11.0 | Bridge ecosystem kickoff (rune_cupertino) |
Public-API stability pact from v1.0.0 held across all twelve releases. Zero breaking changes. Main package passes 1619 root tests plus architecture invariants plus 7 sibling tests. rune_cupertino adds another 72 tests.
1.10.0 - 2026-04-19 - developer experience #
Added #
- "Did you mean" diagnostic suggestions. All three main
exception types gained factory constructors that compute a
Levenshtein-distance suggestion from a candidate pool.
UnregisteredBuilderException.withSuggestionreads widget, value, and component registry names;ResolveException.withSuggestionreceives the current target type's methods / properties;BindingException.withSuggestionreads data keys plus scope names. When a close enough match exists (Levenshtein distance within a reasonable bound) the exception message appends(did you mean "X"?). Source-level typos likeColums(...)or.toUppercase()now get clear diagnostics. - Widened source pointer.
SourceSpan.toContextualPointer( fullSource, contextLines: N)renders the carat-pointer with N lines of context above and below the offending line. The defaulttoPointerStringstays unchanged (single-line excerpt for backwards compatibility); consumers who want more context call the new method in theironErrorhandler withctx.source(orRuneView.source) as the full-source argument. - Source formatter.
formatRuneSource(String source, {int maxLineLength = 80})returns a canonically-formatted version of the input: 2-space indent per level, per-argument breaks inside multi-line calls, trailing commas on multi-line lists / arg lists, single-line collapse when the whole expression fits. Unparseable input returns unchanged with a diagnostic comment header. Useful as a programmable formatter for Rune sources in tooling pipelines. - Registry introspection.
Registry<T>.names,ValueRegistry.typeNames,ComponentRegistry.typeNames,ConstantRegistry.memberNamesOf(typeName),ExtensionRegistry.names,RuneDataContext.keys, andRuneScope.namesexpose the registered / bound names so consumers (and the suggestion factories above) can enumerate candidates without leaking the backing maps.
Notes #
formatRuneSourceis exported from the public barrellib/rune.dart.- Levenshtein helper lives in
lib/src/core/levenshtein.dartas a pure core utility (no Flutter or analyzer deps). Threshold tuning (max distance, minimum candidate length) is internal; future releases can expose the knobs if needed. - About 46 new tests: 13 Levenshtein unit tests, 6
toContextualPointertests, 4 exception-factory tests, 13 formatter tests, 10 resolver diagnostic-suggestion integration tests. - The v1.0.0 stability commitment holds. Zero breaking changes; existing diagnostic messages keep their exact prefix and gain the suggestion suffix only when a close match exists.
Deferred #
- DevTools extension ships as a separate package
(
packages/rune_devtools_extension/) in a future release. Hot source reload, AST inspector, and state bag inspector belong there rather than in the main package. - VSCode syntax highlighting extension ships as a separate repo.
- LSP autocomplete remains out of scope.
1.9.0 - 2026-04-19 - explicit animations #
Added #
- AnimationController. Source declares an AnimationController
inside a
StatefulBuilder'sinitialmap. The value builder returns anAnimationControllerSpec(a pure data descriptor);StatefulBuilder's private host state now mixes inTickerProviderStateMixinand swaps each spec in-place for a realAnimationController(vsync: this, ...)before any source code touches the state. The host tracks owned controllers and disposes them in itsdispose, skipping them in theautoDisposeListenablessweep to avoid double-disposal. Source calls.forward(),.reverse(),.stop(),.reset(), and.repeat()via the builtin-method whitelist; property access covers.value,.status,.isAnimating,.isCompleted,.isDismissed. - Tween value builders.
Tween(begin, end)(generic over any runtime value),ColorTween(begin?, end?), andCurvedAnimation(parent, curve, reverseCurve?). - Transition widget builders.
FadeTransition(opacity, child),SlideTransition(position, child),ScaleTransition(scale, child, alignment?),RotationTransition(turns, child, alignment?),SizeTransition(sizeFactor, child, axis?, axisAlignment?). Each takes a Flutter Animation value (typically from a tween driven by an AnimationController). - AnimatedBuilder.
AnimatedBuilder(animation, builder: (BuildContext, Widget?) -> Widget, child?). The builder closure receives an optional child that Flutter rebuilds only when the animation ticks, matching the vanilla AnimatedBuilder contract. - AnimationStatus constants.
dismissed,forward,reverse,completedjoin the constants table. - Animation Read
.value,.status,.isAnimating,.isCompleted,.isDismissedon any Animation
Notes #
- Widget count 109 to 115. Value builder count 50 to 54. Constants gain AnimationStatus.
- About 77 new tests across the six transition widgets, the five value builders (AnimationController, Tween, ColorTween, CurvedAnimation, AnimatedBuilder), the AnimationControllerSpec materialization path in StatefulBuilder, the builtin-method whitelist extensions, and a five-scenario integration smoke (rotating icon with repeat, fading text via forward, sliding panel with tween + curve, animated builder closure, AnimationStatus reach).
ListenableBuilderstays deferred (AnimatedBuilder accepts anyListenablevia itsanimationparameter, covering the common case; ListenableBuilder is a thin alias and can ship as a one-file follow-up).PageRouteBuilder+Navigator.popUntil(predicate closure) remain deferred from v1.6.0; their closure signatures mirror the now-supportedAnimatedBuilderand could land in a future patch..drive(Animatable)on AnimationController stays out of scope; source users construct the Animation via CurvedAnimation + tween.animate(...) if needed, but.animateitself is not yet whitelisted. Future patch candidate.- The v1.0.0 stability commitment holds. Zero breaking changes.
Existing StatefulBuilder source continues to work unchanged
(spec materialization only touches entries that happen to be
AnimationControllerSpec).
1.8.0 - 2026-04-19 - data tables and structured display #
Added #
- DataTable.
DataTable(columns: List<DataColumn>, rows: List<DataRow>, sortColumnIndex?, sortAscending?, columnSpacing?, headingRowHeight?, dataRowMinHeight?, dataRowMaxHeight?, showBottomBorder?, dividerThickness?). Column sorting is driven by a closure onDataColumn.onSort: (columnIndex, ascending) -> void. - DataColumn, DataRow, DataCell value builders.
DataColumn(label, numeric?, tooltip?, onSort?),DataRow(cells, selected?, onSelectChanged?, color?),DataCell(child, onTap?, showEditIcon?, placeholder?). Compose into typed lists passed into DataTable. - ExpansionTile.
ExpansionTile(title, children?, subtitle?, leading?, trailing?, initiallyExpanded?, onExpansionChanged?, backgroundColor?, collapsedBackgroundColor?, iconColor?, textColor?, tilePadding?, childrenPadding?).onExpansionChangedis a(bool) -> voidclosure routed through the existingvalueEventCallback<bool>. - ExpansionPanelList + ExpansionPanel.
ExpansionPanelList( children: List<ExpansionPanel>, expansionCallback: (int, bool) -> void, animationDuration?, expandedHeaderPadding?)composes a list ofExpansionPanel(headerBuilder: (BuildContext, bool) -> Widget, body: Widget, isExpanded?, canTapOnHeader?)value entries. - Stepper family.
Stepper(steps: List<Step>, currentStep, type?, onStepTapped?, onStepContinue?, onStepCancel?)plus aStep(title, content, subtitle?, isActive?, state?)value builder. Default controls render automatically; customcontrolsBuilderstays deferred (Flutter'sControlsDetailsshape is not a clean value-builder candidate). - Closure helpers.
toIntBoolCallback((int, bool) -> voidforDataColumn.onSortandExpansionPanelList.expansionCallback),toIntValueChanged((int) -> voidforStepper.onStepTapped),toExpansionPanelHeaderBuilder((BuildContext, bool) -> WidgetforExpansionPanel.headerBuilder) joinlib/src/builders/closure_builder_helpers.dart. - Stepper constants.
StepperType.vertical/.horizontalandStepState.indexed/.editing/.complete/.disabled/.errorjoin the constants table.
Notes #
- Widget count 105 to 109. Value builder count 45 to 50. Constants gain StepperType and StepState.
- About 60 new tests across the four widget builders, the five value builders, the closure helper extensions, and three integration smokes: DataTable with columns and rows, ExpansionTile expand-on-tap, Stepper advance on Continue.
PaginatedDataTablestays deferred; itsDataTableSourcecontract wants lifecycle-aware mutable state that fits better alongside a future data-binding bridge (not v1.x scope).- The v1.0.0 stability commitment holds. Zero breaking changes.
1.7.0 - 2026-04-19 - gestures and advanced interaction #
Added #
- Drag-and-drop.
Draggable<Object>andLongPressDraggable<Object>widget builders takedata,feedback,child, optionalchildWhenDragging, and closure callbacks foronDragStarted/onDragEnd.DragTarget<Object>renders its builder with(BuildContext, List<Object?>, List<Object?>)and acceptsonAcceptWithDetails/onWillAcceptWithDetailsas String event names or closures. - Dismissible.
Dismissible(key, child, onDismissed, direction?, background?).onDismissedclosure receivesDismissDirection. Requires aValueKey(see below). - InteractiveViewer.
InteractiveViewer(child, minScale?, maxScale?, panEnabled?, scaleEnabled?, boundaryMargin?)for pan-and-zoom content. - ReorderableListView.
ReorderableListView(children, onReorder: (oldIndex, newIndex) -> void, padding?, scrollDirection?). Children must carry stable keys; the newValueKeyvalue builder (and a newkey:slot onListTile) cover the common case. - ValueKey value builder.
ValueKey(value)wraps any runtime value into a stable widget key. TheListTilebuilder now accepts an optionalkeyso it can serve as a keyed child of ReorderableListView. - Closure helpers.
toDragTargetBuilder(3-arg),toDismissibleCallback,toReorderCallback,toDragEndCallback,toDragAcceptCallback,toDragWillAcceptCallbackjoinlib/src/builders/closure_builder_helpers.dart. Every closure-wrapping contract for gesture widgets lives in the same place per the shared-helper-first discipline. - DismissDirection constants.
endToStart,startToEnd,up,down,horizontal,vertical,nonejoin the constants table.
Notes #
- Widget count 100 to 105 (ListTile keeps its typeName; the six new gesture widgets bring the total up 5). Value builder count 44 to 45 (ValueKey). Constants gain DismissDirection.
- About 45 new tests across the six gesture widgets, the ValueKey value builder, the closure helper extensions, and four integration smokes (Draggable + DragTarget drop, Dismissible swipe, ReorderableListView reorder, InteractiveViewer pan).
- The v1.0.0 stability commitment holds. Zero breaking changes.
ListTile.keyis purely additive; existing ListTile source that did not supply a key continues to render identically.
1.6.0 - 2026-04-19 - navigation and routing #
Added #
- Navigator imperative bridges.
Navigator.push(route),Navigator.pushReplacement(route),Navigator.pushNamed(name, arguments?), andNavigator.canPop()join the v1.3.0Navigator.popbridge. All route throughRuneContext.flutterContext;canPopreturns a bool, the push variants discard theirFuture<T?>return. Dispatched through a new_navigatorBridgeswhitelist map inInvocationResolverthat replaces the previous hard-codedpop-only special case. - Route value builders.
MaterialPageRoute(builder, settings?, fullscreenDialog?, maintainState?),CupertinoPageRoute(builder, title?, settings?, fullscreenDialog?, maintainState?), andRouteSettings(name?, arguments?). Builder closures follow the v1.3.0toContextWidgetBuildercontract.
Notes #
- Value builder count 41 to 44. Widget count unchanged. Constants unchanged.
- About 30 new tests across the three route value builders, the four Navigator bridges, and two integration smokes: push + pop round-trip through MaterialPageRoute, pushNamed flow via MaterialApp.routes.
PageRouteBuilderstays deferred. Its closure-basedpageBuilder/transitionsBuilderargs takeAnimation<double>parameters; property access onAnimation<double>(.value,.status) lands in v1.9.0's explicit-animation release together with theAnimationControllerstory.Navigator.popUntil(predicate)deferred for the same reason. Predicate closures take aRouteargument; extending the closure-helper surface for non-BuildContext arguments fits naturally alongside the v1.9.0 animation closures.- The v1.0.0 stability commitment holds. Zero breaking changes.
1.5.0 - 2026-04-19 - forms, validation, focus #
Added #
- Form widget.
Form(child, onChanged?, autovalidateMode?).onChangedfires whenever any child FormField changes (VoidCallback viavoidEventCallback).autovalidateModeaccepts the newAutovalidateModeenum constants.canPop/onPopInvokedare deferred to v1.6.0's navigation work. - TextFormField. A text input with validation. Required / typical
args:
value(initial text),controller(external TextEditingController),validator(a 1-arg closure(String?) -> String?returning a validation message or null),onSaved(1-arg closure(String?) -> void),onFieldSubmitted(String event name or 1-arg closure),onChanged(same), plus all visual args TextField already had (hintText, labelText, obscureText, maxLines, etc.).autovalidateModeper field. - Focus and FocusScope widget builders.
Focus(child, autofocus?, focusNode?, canRequestFocus?, onFocusChange?)wraps a subtree in a focus node (external node supplied via the v1.1.0FocusNodevalue builder).FocusScope(child, autofocus?, canRequestFocus?, onFocusChange?)creates a scoped focus tree node. TextField.focusNode.TextFieldnow accepts an optionalfocusNode: FocusNodeplumbed through the_RuneTextFieldinternal wrapper, closing a v1.1.0 integrity gap where theFocusNodevalue builder existed but no widget consumed it.- Closure helpers.
toValidator((String?) -> String?) andtoStringValueChanged((String?) -> void) joinlib/src/builders/closure_builder_helpers.dart. Both reuse the existing arity / type validation shared with the v1.2.0 and v1.4.0 helpers. - AutovalidateMode constants.
always,onUnfocus,onUserInteraction,disabledjoin the constants table.
Notes #
- Widget count 96 to 100. Value builder count unchanged. Constants gain AutovalidateMode.
- About 40 new tests across the four widget builders, the closure helpers, and three integration smokes: Form + TextFormField validator surfacing the error on user interaction, Focus + FocusNode transferring focus across a button press, FocusScope with autofocus granting focus to an autofocus child.
onFieldSubmittedusesvalueEventCallback<String>(Flutter's ValueChanged- A CI-portability fix on the Form-validator integration smoke
swapped
AutovalidateMode.always(which behaves differently between Flutter 3.24 and newer versions on initial render) forAutovalidateMode.onUserInteraction, then drove the test via two sequentialenterTextcalls (short invalid, then valid). The original intent (Form + TextFormField + validator round-trip) is preserved; only the triggering pattern changed.
1.4.0 - 2026-04-19 - theme access, M3 widgets, date/time pickers #
Added #
- Context accessors.
Theme.of(context)andMediaQuery.of(context)resolve as well-knownMethodInvocationshapes at the resolver level. Both return the raw Flutter value (ThemeData,MediaQueryData), whose property access routes through the built-in property whitelist. Source can now read the active theme's colors, text styles, and device metrics without leaving the string. - Read-only property whitelist for Flutter data types.
ThemeData.colorScheme,.textTheme,.brightness,.primaryColor,.useMaterial3,.scaffoldBackgroundColor,.cardColor,.dividerColor.ColorScheme.primary,.onPrimary,.secondary,.onSecondary,.tertiary,.error,.onError,.surface,.onSurface,.surfaceContainerHighest,.outline,.shadow,.brightness, plus tonal inverse / variant slots.TextTheme.bodyLargethroughbodySmall,titleLarge/Medium/Small,headlineLarge/Medium/Small,labelLarge/Medium/Small.MediaQueryData.size,.orientation,.padding,.viewInsets,.viewPadding,.devicePixelRatio,.textScaler,.platformBrightness.Size.width,.height,.shortestSide,.longestSide,.aspectRatio,.isEmpty.EdgeInsets.left,.top,.right,.bottom,.horizontal,.vertical. - Material 3 widget builders.
FilledButton,OutlinedButton,SegmentedButton(generic onObject?values withmultiSelectionEnabledandonSelectionChangedclosure),SearchBar, andSearchAnchor.bar(a closure-drivensuggestionsBuilder). - Theme-related value builders.
ColorScheme.fromSeed(seedColor, brightness?),ThemeData(colorScheme, useMaterial3, brightness, scaffoldBackgroundColor, cardColor, dividerColor),ButtonSegment(value, label, icon, tooltip, enabled). - Date / time value builders.
DateTime(year, month?, day?, hour?, minute?, second?, millisecond?, microsecond?)positional constructor andTimeOfDay(hour: int, minute: int)named constructor. Source can build the bounds for the picker bridges without host pre-computation. - Date / time imperative bridges.
showDatePicker(context?, initialDate, firstDate, lastDate, helpText?, cancelText?, confirmText?)andshowTimePicker(context?, initialTime, helpText?, cancelText?, confirmText?). Both resolve throughRuneContext.flutterContext; the returnedFutureis discarded in source and available to the host via custom event handlers. - Constants.
ThemeMode.light/.dark/.system,Brightness.light/ .dark,MaterialTapTargetSize.padded/.shrinkWrap.
Notes #
- Widget count 91 to 96; value count 35 to 41. Constants table gains ThemeMode, Brightness, MaterialTapTargetSize.
- About 68 new tests across the context-accessors resolver path,
the new widgets and value builders, the imperative bridges, and
integration smokes driving theme-aware containers, FilledButton
- OutlinedButton rendering, SegmentedButton multi-select toggling, and a date-picker launch.
FilledButton.tonalstays deferred; v1.0.0's stability pact holds, existing.tonalconsumers useFilledButtonplus a custom style in host code.- A mid-cycle dartdoc-reference fix narrowed two CI-blocking
comment_referencesfindings ondate_time_builder.dartby rewriting the constructor-signature dartdoc to avoid bracketed optional-param syntax (the CI-pinned analyzer flags[name]even inside backtick-quoted code spans).
1.3.0 - 2026-04-19 - dialogs, overlays, imperative bridges #
Added #
- Dialog family.
AlertDialog,SimpleDialog,SimpleDialogOption,Dialogwidget builders cover the standard Material dialog shapes.AlertDialogaccepts title / content / actions / icon plus full theming.SimpleDialogOptiontakesonPressedas a String event name or closure. - Popup menus.
PopupMenuButton,PopupMenuItem,PopupMenuDivider.PopupMenuButton.itemBuilderis a(BuildContext) -> List<PopupMenuEntry>closure;onSelectedreceives the tapped item's value through the callback dispatch contract.PopupMenuDividerexposesheightfor now; newer Flutter-only params (thickness, indent, color) are deferred until the pinned-CI Flutter catches up. - SnackBar value builder.
SnackBar(content, action?, duration?, backgroundColor?, behavior?)constructs the SnackBar that theshowSnackBarimperative bridge consumes.SnackBarBehavior.fixed/.floatingjoin the constants table. - Imperative bridges.
showDialog(builder, barrierDismissible?),showModalBottomSheet(builder, isScrollControlled?, backgroundColor?),showSnackBar(snackBar), andNavigator.pop(result?)are recognised at the resolver level as bare-identifier invocations that route throughRuneContext.flutterContext. Each helper raises a clear ResolveException when the context is null (source invoked outside a live RuneView render). - Closure helpers.
toContextWidgetBuilderandtoPopupMenuItemBuilderjoinlib/src/builders/closure_builder_helpers.dartalongside the v1.2.0 builder-callback adapters.
Notes #
- Widget count 84 to 91; value count 34 to 35. Constants table gains SnackBarBehavior.
- About 57 new tests across the seven widget builders, the SnackBar value builder, the four imperative bridges, and five integration smokes (showDialog with AlertDialog, PopupMenuButton selection, showSnackBar notification, showModalBottomSheet, Navigator.pop from inside a dialog).
BottomSheetwidget andshowMenuimperative bridge are deferred:BottomSheetrequiresonClosingand is better expressed throughshowModalBottomSheet, andshowMenuneeds customRelativeRectpositioning outside v1.3.0 scope.SnackBarActionvalue builder deferred;action: nullis fine for this release.
Fixed #
PopupMenuDividernarrowed to theheightparameter only. The Flutter version pinned to CI (3.24) rejects thethickness/indent/endIndent/colornamed params. Local dev Flutter accepted them, soe652789originally landed with a version-mismatch analyzer error;7d764c1trimmed the surface so both Flutter versions compile cleanly.
1.2.0 - 2026-04-19 - closure-based builder widgets #
Added #
- Lazy list and grid builders.
ListView.builder(itemCount, itemBuilder),GridView.countBuilder(crossAxisCount, itemBuilder, itemCount?)plus a matchingGridView.extentBuilder(maxCrossAxisExtent, ...). Each takes a(BuildContext, int) -> Widgetclosure. Source can now render thousands of items without eagerly materialising them. Sliver counterparts (SliverList.builder,SliverGrid.countBuilder,SliverGrid.extentBuilder) compose intoCustomScrollViewdirectly. - Async builders.
FutureBuilder(future, builder)andStreamBuilder(stream, builder, initialData?). Host supplies theFuture/StreamthroughRuneView.data; the builder receives(BuildContext, AsyncSnapshot)and returns a Widget.AsyncSnapshot.hasData,.data,.hasError,.error,.connectionStatework through the property-access whitelist.ConnectionState.none/.waiting/.active/.donejoin the constants table. - Layout and orientation builders.
LayoutBuilder(builder)yields the parent'sBoxConstraintsso source can choose a layout at build time.BoxConstraints.maxWidth,.minWidth,.maxHeight,.minHeight,.biggest,.smallestare whitelisted property accesses.OrientationBuilder(builder)yieldsOrientation.portraitor.landscape(both registered as constants). - Closure builder helpers.
lib/src/builders/closure_builder_helpers.dartholds the shared closure-to-Flutter-callback adapters:toIndexedBuilder,toFutureSnapshotBuilder,toStreamSnapshotBuilder,toLayoutBuilder,toOrientationBuilder. Arity + type validation lives in one place per the shared-helper-first discipline.
Fixed #
- Dispatch precedence for named-constructor invocations.
InvocationResolvernow checks the value registry first when aMethodInvocationorInstanceCreationExpressioncarries aconstructorName(e.g.ListView.builder(...)). Previously a bare widget builder registered under the sametypeName(ListView) would shadow any value-builder named-constructor variants (ListView.builder). Widget-first precedence remains for bare invocations (Text('hi')); only the named-ctor path changed. This fix unblocked the entire v1.2.0 .builder family and is covered by the new builder tests.
Notes #
- Value count 28 to 34; widget count 80 to 84.
- 82 new tests across the ten new builders, closure helper suite,
AsyncSnapshot/BoxConstraintsproperty access, and five integration smokes (lazy ListView, LayoutBuilder responsive split, FutureBuilder with host-provided future, OrientationBuilder, SliverList.builder inside CustomScrollView). BuildContextpassed to closures remains opaque in v1.2.0. TheTheme.of(context)/MediaQuery.of(context)accessors arrive in v1.4.0.
1.1.0 - 2026-04-19 - lifecycle and controllers #
Added #
- StatefulBuilder lifecycle closures.
initState,dispose, anddidUpdateWidgetoptional closure params let source own the full mount / unmount / rebuild lifecycle of its state bag.autoDisposeListenables: true(defaultfalse) callsdisposeon anyinitialentry that implementsChangeNotifierwhen the widget unmounts. The source-level dispose closure runs first, then auto-disposal, so the source can perform ordered cleanup. - Controller value builders.
TextEditingController(text),ScrollController(initialScrollOffset, keepScrollOffset, debugLabel),FocusNode(debugLabel, skipTraversal, canRequestFocus, descendantsAreFocusable, descendantsAreTraversable), andPageController(initialPage, keepPage, viewportFraction)join the default value-builder set.TabControllerstays deferred pending v1.9.0's vsync story (shared withAnimationController). - Controller method whitelist.
invokeBuiltinMethodgains controller dispatch forTextEditingController.clearplus.text/.valuegetters;ScrollController.jumpToand.animateTo;FocusNode.requestFocus,.unfocus,.hasFocus;PageController.jumpToPageand.animateToPage;TabController.animateToand.indexfor host-provided instances. - Widget controller wiring.
TextField,ListView,SingleChildScrollView,CustomScrollView, andTabBaraccept an optionalcontroller:arg so source-constructed controllers reach the widgets that should own them.TextField's private state keeps the existing internal-controller path as the default and switches to the external controller when supplied; disposal stays with whichever side constructed the controller.
Notes #
- Value count 24 to 28. No new widget count growth (the controller wiring extends existing builders' arg surface without adding new typeNames). About 43 new tests across the controller builders, the lifecycle hooks, the method whitelist, and two integration smokes (controller lifecycle with a persistent TextEditingController, programmatic scroll with source-owned ScrollController).
- The v1.0.0 stability commitment holds. No breaking changes; existing source that did not supply a controller continues to work unchanged.
1.0.0 - 2026-04-19 - stateful source end-to-end #
Added #
- Named components (Phase F of the v1.0.0 roadmap). Source can
declare reusable components and invoke them by name. New
RuneComponentvalue type carries a name, parameter list, and body closure. NewComponentRegistryholds components per-RuneView (source-scoped, not global; a fresh registry every render). NewRuneComponentBuilder(value builder) constructs and registers a component; newRuneComposewidget builder accepts acomponents: [...]declaration list and aroot:widget tree.InvocationResolverdispatches against the component registry before the widget and value registries, so a component namedTextshadows the default Text builder. Invocation is named-arg only; positional args or named constructors raiseResolveException; missing or extra named arguments raise as well. Thirty-two new unit tests plus three integration smokes drive single-use, multi-use, and nested components through a liveRuneView.
Notes #
- Phase E of the v1.0.0 roadmap (collection method chaining with
closures) shipped informally during Phase A.3 (v0.9.0) as
.map,.where,.fold,.reduce, and friends. v1.0.0 acknowledges it as complete; no additional resolver work. - Stability commitment. The public API surface is stable for early adopters. Post-1.0.0 minor releases add new builders, resolver arms, and source-language capabilities; patch releases cover fixes and refinements. Breaking changes warrant a major bump or explicit deprecation cycle.
- Full public surface from
lib/rune.dart:RuneView,RuneConfig,RuneDefaults,RuneContext,RuneException(sealed plus five variants),RuneBuilder/RuneWidgetBuilder/RuneValueBuilder,ResolvedArguments,RuneDataContext,RuneEventDispatcher,RuneBridge,RuneDevOverlay,ExtensionRegistry,RuneExtensionHandler,Registry,WidgetRegistry,ValueRegistry,ConstantRegistry,ComponentRegistry,RuneComponent,RuneClosure,RuneScope,RuneState,SourceSpan.
0.11.0 - 2026-04-19 - source-level state and setState sugar #
Added #
- Source-level state via StatefulBuilder and RuneState (Phase C
of the v1.0.0 roadmap). New
RuneStatevalue type (inlib/src/core/rune_state.dart) carries a mutable string-keyed bag readable from source through the existing Map-first branch ofPropertyResolverandIdentifierResolver. Writes go throughstate.set(key, value),state.setMany({...}),state.remove(key), andstate.clear(); each mutating operation fires anonMutationcallback bound to the hosting widget'ssetState. NewStatefulBuilderwidget builder (registered as a default) takes a requiredinitialmap to seed the state on first mount plus a requiredbuilderclosure that receives the state and returns a Widget; mutations that fire mid-build schedule a post-framesetStateinstead of re-entering synchronously, so infinite rebuild loops are impossible.invokeBuiltinMethodgrows aRuneStatebranch so source can callstate.set,state.setMany,state.remove,state.clear,state.get,state.has. setStatesugar andRuneStateproperty-access assignment (Phase D of the v1.0.0 roadmap). Source can now writestate.counter = state.counter + 1(PrefixedIdentifier left-hand-side) or(expression).counter = v(PropertyAccess LHS); the assignment routes throughRuneState.set(memberName, rhs)and triggers exactly one rebuild via the Phase C mutation callback.InvocationResolveralso recognises the baresetState(() { ... })identifier with a no-arg closure and runs the closure; because Phase C mutations already trigger rebuilds, the wrapper is a semantic passthrough that exists to match Flutter's conventional pattern. Assignment to a non-RuneStateprefix / target raisesResolveExceptionciting the offending type; compound operators (+=,-=, etc.) stay rejected;setStatecalls with wrong arity or non-closure args raise clearResolveExceptionmessages.
0.10.0 - 2026-04-19 - block-body closures and local scope #
Added #
- Block-body closures with local scope and assignment (Phase B
of the v1.0.0 roadmap).
(x) { ... }closures now parse and execute; the Phase A.1 arrow-only restriction is lifted.RuneClosuregains two named constructors (.expressionfor the Phase A.1 arrow-body shape,.blockfor Phase B). Block-body closures create a freshRuneScopeon each call and walk the body's statements via the newStatementResolver. Earlyreturnshort-circuits the sequence and yields the returned value; block bodies without a return yieldnull, matching Dart. - Local scope via
RuneScope.varandfinaldeclarations inside a block body live in a mutableRuneScope(lib/src/core/rune_scope.dart) with parent-chaining for nested blocks.declare()enforces no re-declaration in the same scope;assign()walks outward to find the declaring scope and raisesBindingExceptionwhen no scope owns the name. Distinct fromRuneDataContext, which stays immutable and host-owned.RuneContextgains an optional nullablescopefield (pluscopyWitharg), andIdentifierResolver.resolveSimplenow checksctx.scopebeforectx.dataso block-body locals shadow host-provided data keys of the same name, matching Dart's lexical scoping. - Statement-level execution. New
lib/src/resolver/statement_resolver.dartdispatchesExpressionStatement,ReturnStatement,VariableDeclarationStatement, andIfStatement, plus nestedBlockas a child scope. Unsupported statements (loops,try/catch,switch) raiseResolveExceptionpointing at the offending node. - Assignment expressions for locals.
ExpressionResolvergains anAssignmentExpressionarm for the=operator withSimpleIdentifieron the left. Assigning to a host-supplied data name is forbidden with a clear diagnostic (data mutation is Phase C territory). Compound operators (+=,-=) and assignment toPropertyAccess/IndexExpressionleft-hand-sides stay deferred to Phase D. AbindStatementshook mirrors the existingbind/bindPropertypattern; the live pipeline uses a lazy self-boundStatementResolversodynamic_view.dartneeds no change.
0.9.0 - 2026-04-19 - closures in source #
Added #
- Closures in source (Phase A of the v1.0.0 roadmap).
(x) => x + 1now parses as a first-class value. The resolver gains a newFunctionExpressionarm that producesRuneClosures, captures the enclosingRuneContext, and re-enters the expression resolver on each call with the closure's arguments bound alongside the captured data. Arrow-body form only in this release; block-body closures ((x) { return x; }) are deferred to a later phase with a clearResolveExceptionmessage. - Builder callbacks accept closures. Every event-accepting widget
builder now accepts either a
Stringevent name (existing behavior, unchanged) or a closure.ElevatedButton(onPressed: () => state.counter + 1, ...)routes through the widget's Flutter callback and invokes the closure with the event's arguments.valueEventCallback<T>forwards the bool/int/String/etc. value as the single positional argument to the closure;voidEventCallbackcalls the closure with an empty args list. - Collection methods with closures.
invokeBuiltinMethodgrows eight new closure-accepting methods onList:.map,.where,.any,.every,.firstWhere,.forEach,.fold,.reduce..mapand.wherereturn materialisedList<Object?>(not lazyIterable) so downstream builders see concrete lists..any/.every/.firstWhere/.wherevalidate that the closure's return is abool..foldtakes an initial value plus a two-parameter combiner..reducetakes a two-parameter combiner; empty lists propagate Dart's ownStateError('No element').
Notes #
- Phase A.1, A.2, and A.3 landed as four commits on top of v0.8.0.
The architecture test gained a dedicated guard for the new
lib/src/builders/event_callback.darttolib/src/resolver/rune_closure.dartedge, ensuring future changes cannot silently pull unrelated resolver files into the builders layer. - Phase B (block-body closures, local variable declarations, scoped mutation) is next on the v1.0.0 roadmap and ships in v0.10.0.
0.8.0 - 2026-04-19 - pragmatic gaps #
Added #
- Transform.translate, Transform.flip, and Offset. Closes the
Transform value-builder family started in v0.7.0 (which shipped
Transform.scaleandTransform.rotatebut deferred translate pending anOffsetvalue builder).Offset(dx, dy)takes two positional nums coerced to double.Transform.translaterequires anoffsetplus optionalchildandtransformHitTests.Transform.fliptakes optionalflipX/flipYbooleans (both default false) plustransformHitTestsand optionalchild; alignment is fixed toAlignment.centerby Flutter's own constructor. - Sizing primitives. Four constraint-manipulation widgets plus
one value builder for the constraints themselves.
ConstrainedBoxapplies arbitraryBoxConstraintsto its child (requiredconstraints).LimitedBoxcaps the child only when the parent offers unbounded constraints along that axis; optionalmaxWidth/maxHeightdefault todouble.infinity.UnconstrainedBoxdiscards parent constraints along one axis or both; optionalconstrainedAxis,alignment,clipBehavior.FractionallySizedBoxsizes its child to a fraction of the parent; optional nullablewidthFactor/heightFactorplusalignment.BoxConstraints(minWidth, maxWidth, minHeight, maxHeight)joins the value-builder set; all four edges default to Flutter's own (0 for mins, infinity for maxes). - Form input tiles.
CheckboxListTile,SwitchListTile,RadioListTilecombine the existing Checkbox / Switch / Radio builders with aListTilelayout (title, subtitle, secondary, controlAffinity). All follow the two-way binding contract; the tile dispatches(onChanged, [newValue])on tap.CheckboxListTileandRadioListTilediscriminate absent value (ArgumentException) from explicitvalue: null(legitimate for tristate / radio-deselect).ListTileControlAffinity.leading,.trailing,.platformjoin the constants table. MaterialColor[shade]index access.Colors.grey[200]now resolves in Rune source. Integer shade lookups on any registeredMaterialColorgo throughExpressionResolver._resolveIndex. Non-int indices raiseResolveException; unknown shades returnnull, matching Flutter's ownMaterialColor.operator[]semantics.- Material 3 navigation.
NavigationBarwithNavigationDestinationreplaces the Material 2 pattern (BottomNavigationBar/BottomNavigationBarItemstill ship for consumers that need Material 2 theming). Requireddestinations(at least 2) andselectedIndex; optionalonDestinationSelectedevent, plus theming viabackgroundColor,elevation,height,indicatorColor.NavigationRailis the landscape / tablet variant withextendedtoggle, optionallabelType(NavigationRailLabelTypeenum:none,selected,all),leading,trailing, and sizing (minWidth,elevation).NavigationRailDestinationcarriesicon+labelWidgets plus optionalselectedIconandpadding. - Chip variants.
ChoiceChip(single-select) andFilterChip(multi-select toggle) complement the existingChipinfo-tag builder. Both requirelabel(Widget) andselected(bool); optionalonSelectedevent name dispatches(name, [newBool])on tap plus avatar and colour theming.FilterChipalso acceptscheckmarkColorandshowCheckmark(default true). ActionChip and InputChip remain deferred pending a concrete use case.
0.7.0 - 2026-04-19 - wrappers, slivers, transforms #
Added #
- Wrapper and utility widgets. Seven everyday builders for
composition, visibility, and masking:
Drawer(side menu content forScaffold.drawer; optionalbackgroundColor,elevation,width),SafeArea(system-inset avoidance; per-edge toggles +minimum/maintainBottomViewPadding),Visibility(conditional render with replacement + optional maintainState/Animation/Size),Opacity(static non-animated complement toAnimatedOpacity),ClipRRect(rounded-corner clip withborderRadius+clipBehavior),ClipOval(circular clip),Tooltip(message + preferBelow/wait/show durations + padding;richMessagedeferred).Clip.none/hardEdge/antiAlias/ antiAliasWithSaveLayerjoin the constants table for the two clip builders. - Slivers and CustomScrollView. Compose advanced scrollable
layouts that plain
ListView/GridViewcan't express.CustomScrollViewdrives thesliverslist; sliver primitives includeSliverList(children-based, closure variant deferred),SliverGrid.countandSliverGrid.extent(value-builder named ctors),SliverToBoxAdapter(wrap any non-sliver widget),SliverAppBar(collapsing app bar withpinned/floating/snap+expandedHeight+flexibleSpace),SliverPadding(edge padding around a sliver),SliverFillRemaining(fills the rest of the viewport). Closure-param sliver variants (SliverList.builder,SliverGrid.builder, etc.) stay deferred pending function-literal support in source. - Display wrappers and programmatic transforms. Five
display-layer widget builders and two Transform value ctors.
FittedBox(scale child to fit;fit: BoxFit,alignment),ColoredBox(efficient leaf for solid color fills),DecoratedBox(Container without padding/margin overhead; requireddecoration, optionalposition),Offstage(renders invisible but state preserved),Semantics(accessibility label/value/hint/button/link/header/image flags).Transform.scale(uniform viascaleor axis-specific viascaleX/scaleY) andTransform.rotate(requiredanglein radians).DecorationPosition.backgroundand.foregroundjoin the constants table for DecoratedBox.Transform.translateis deferred until the Offset value builder lands alongside.
0.6.0 - 2026-04-19 - widget breadth (Material, animations, grids) #
Added #
- Material widget breadth. Five more everyday builders.
FloatingActionButton(onPressed event, optional child/tooltip/ colors/mini),Chip(required label, optional avatar, onDeleted event, colors/style),Badge(wraps a child with an optional label overlay; supports backgroundColor/textColor/smallSize/ largeSize/isLabelVisible),CircularProgressIndicatorandLinearProgressIndicator(both indeterminate by default, setvalue: 0.0-1.0for determinate). - Animation expansions. Four more animated widget builders
complementing the AnimatedContainer/Opacity/Positioned trio from
v0.5.0:
Hero(cross-route shared-element transitions; required non-nulltag+child, optionaltransitionOnUserGestures),AnimatedSwitcher(fade between children when the child's key changes; requiredduration, optionalreverseDuration/switchInCurve/switchOutCurve),AnimatedCrossFade(fade between two declared children driven by aCrossFadeStateenum; requiredfirstChild/secondChild/crossFadeState/durationplus optional first/second/size curves and alignment), andAnimatedSize(animate own size to match child; requiredduration+ optional curve/alignment/reverseDuration).CrossFadeState.showFirst/.showSecondjoin the constants table. Closure-shaped args (createRectTween,flightShuttleBuilder, etc.) remain out of scope pending function-literal support in source. - Grid views.
GridView.count(fixed column count) andGridView.extent(max cell extent) join the default value-builder registry. Both require their primary sizing arg (crossAxisCount: int/maxCrossAxisExtent: num); optionalchildren,mainAxisSpacing,crossAxisSpacing,childAspectRatio,scrollDirection(Axis),padding,shrinkWrap,reverse.GridView.builderis deferred pending function-literal support in source.
0.5.0 - 2026-04-19 - animations + navigation + dropdown #
Added #
- Animated widgets and Duration support.
AnimatedContainer,AnimatedOpacity,AnimatedPositioned. Each takes a requiredduration: Duration(...)and optionalcurve(defaulting toCurves.linear). When the host rebuildsRuneViewwith new values for any tweenable slot (dimensions, colour, opacity, position), Flutter animates between old and new automatically.Duration(milliseconds: n)is a new default value builder; nine canonicalCurves.*instances (linear,easeIn/Out/InOut,bounceIn/Out,elasticIn/Out,fastOutSlowIn) join the constants table. - Navigation widgets.
BottomNavigationBar(requireditemscurrentIndex, optionalonTapdispatching(name, [newIndex]), theming viatype/selectedItemColor/unselectedItemColor/ backgroundColor),TabBar+Tab(assume a host-sideDefaultTabControllerancestor), plusBottomNavigationBarItemas a value builder.BottomNavigationBarType.fixed/.shiftingjoin the constants table.
- Dropdown select.
DropdownButtonwithDropdownMenuItem. Both parametric onObject?so item values can be any runtime type. Requireditems: List<DropdownMenuItem<Object?>>; optionalvalue(absent or explicit null renders the hint),onChanged: Stringevent name,hint,disabledHint,isExpanded. A nullonChangeddisables the dropdown, matching the Switch / Checkbox / Slider / Radio pattern.
Changed #
- Event-callback helpers extracted. Fourteen call sites across
ten builders duplicated the same
eventName == null ? null : () => ctx.events.dispatch(...)pattern (and the value-carrying variant). Consolidated into two shared helpers inlib/src/builders/event_callback.dart:voidEventCallback(name, events)andvalueEventCallback<T>(name, events). Every existing builder test continues to pass unchanged (pure refactor).
0.4.0 - 2026-04-19 - interactive + layout polish #
Added #
- Gesture handlers.
GestureDetectorandInkWellwidget builders, each wrapping a child withonTap,onDoubleTap, andonLongPressnamed-event dispatch.InkWelladds Material ink-splash feedback plus an optionalborderRadiusarg to shape the splash to a rounded container. Any widget now becomes tappable in Rune source without having to be a*Button. - Layout and scroll helpers.
SingleChildScrollView(wraps overflowing content in a scroll region along a chosenAxis),Wrap(Row-like layout that flows to the next line when out of space; supportsdirection/spacing/runSpacing/alignment/runAlignment/crossAxisAlignment),AspectRatio(forces a child to a fixed width-to-height ratio), andPositioned(absolute placement inside aStack).WrapAlignmentandWrapCrossAlignmentenums join the default constants table so source code can referenceWrapAlignment.centerand friends. - Form input widgets:
SliderandRadio. Slider dispatches the newdoublevalue on each drag step; requiredvalue+ optionalmin/max/divisions/label/onChanged. Radio dispatches the selected button's ownvalue(any runtime type) so the host can updategroupValue; supportstoggleablefor tap-to-deselect semantics. Both follow the two-way data-binding contract established by TextField/Switch/Checkbox: the host owns state, interactions fire named events with the new value as the single argument.
Fixed #
- Radio builder's test file used
hide RadioBuilderto avoid a naming collision with a Flutter internal symbol on newer Flutter versions, which broke compilation on the Flutter 3.24.0 pinned in CI (where that symbol isn't exported). Switched to ashow-import of just the needed names so the test compiles on any Flutter >= 3.22.
0.3.0 - 2026-04-19 - runtime-value members + layout helpers #
Added #
- Built-in properties on runtime values.
.length,.isEmpty,.isNotEmpty,.first,.last,.keys,.valueson the Dart primitives they apply to (String, List, Map).PropertyResolverconsults the new table after the Map-key fast-path and before the extension registry, so bridge-registered extensions still win on custom names. - Whitelisted built-in method invocation on runtime values.
toString()on anything;toUpperCase/toLowerCase/trim/contains/ startsWith/endsWith/split/substring/replaceAllon strings;contains/indexOf/joinon lists;containsKey/containsValueon maps;abs/round/floor/ceil/toInt/toDoubleon num. Any other (type, method) pair raisesResolveException. Arbitrary method invocation stays forbidden (whitelist only), matching the store-compliance posture. - Three new widget builders.
ListTile,Divider,Spacer.ListTilecovers the common slots (title,subtitle,leading,trailing) plusonTapas a named event anddense/enabled/selectedflags.Divideracceptsheight,thickness,indent,endIndent,color.Spaceracceptsflex(default 1). With these registered, the Quickstart snippet in the README now runs verbatim againstRuneConfig.defaults().
Changed #
PropertyResolverprecedence is now: Map key (if present), then built-in property (if the pair is recognised), then extension registry. Previously a Map with an absent key returnednullsilently; now if the absent key happens to match a built-in property name (e.g.cart.lengthon a Map with nolengthkey), the Map's own size is returned. Callers relying on the old null-for-absent behaviour for keys that collide with built-in property names should use explicit[…]indexing instead.IdentifierResolver.resolvePrefixedgained the same built-in awareness soitems.length(aPrefixedIdentifier) behaves identically tocart.items.length(aPropertyAccess); both consult the built-in table when the data value is a non-Map type or when a Map lacks the requested key.InvocationResolvernow dispatches runtime method calls on resolved values. When aMethodInvocation's target is aSimpleIdentifierthat doesn't match a registered builder type, or any non-identifier target, the resolver resolves the target, then looks up(runtimeType, methodName)in the whitelist. Builder dispatch still wins forTypeName.ctor(...)shapes whenTypeNameis in the widget or value registry.
0.2.0 - 2026-04-19 - diagnostics + richer source language #
Added #
- Binary and prefix expression operators. Equality (
==,!=), comparison (<,<=,>,>=on num+num or String+String), short-circuit logicals (&&,||), arithmetic (+,-,*,/,%on num), logical not (!on bool), and unary negation (-on num). Out-of-domain operands surface asResolveExceptionwith a source-location pointer. - Conditional rendering. Ternary (
cond ? a : b) as an expression arm, andif-elements in list literals ([if (cond) widget],[if (cond) a else b]). Both short-circuit the un-taken branch, so data keys that are only present in one branch don't need to be defensively populated in the other. - Form input widget builders with two-way data binding.
TextField,Switch,Checkbox. Each accepts avalue(from the host'sdatamap) and anonChangedevent name; user interactions dispatch the new value as a single-element args list throughRuneView.onEvent, leaving the host responsible for updating state.TextFielduses a persistentTextEditingControllerunder the hood so external value updates stay cursor-safe. - Source-location diagnostics. Every
RuneExceptionnow carries an optionalSourceSpan location(new public value class) pointing to where the error originates in the Rune source.toString()renders a caret-pointer block beneath the excerpt when a location is set. Populated by parser diagnostics, every resolver throw site, and bubbled builder argument failures;nullon defensive invariant checks. SourceSpan.fromAstOffset(source, astOffset, astLength)factory. The single source of truth for AST-offset-to-source-location conversion, rebasing analyzer-wrapper offsets and clamping EOF-shaped diagnostics into usable spans.- GitHub Actions CI workflow running
flutter analyze+flutter teston push tomainand on all PRs, across both the root and sibling packages. CONTRIBUTING.md+ GitHub issue/PR templates establishing contributor flow now that the repo is public.
Changed #
RuneContextgained a requiredString sourcefield so resolvers can computeSourceSpans on demand. Production path (RuneView→_buildContext) threadswidget.sourcethrough; the test helper defaults it to an empty string. See "Breaking changes" below.Registry.require,ConstantRegistry.require, andExtensionRegistry.requireeach gained an optionalSourceSpan? locationnamed parameter, threaded through to the thrown exception. Backwards-compatible: existing callers without location keep working.
Fixed #
- Internal code in
lib/src/builders/values/no longer importspackage:rune/rune.dart; barrel imports are reserved for external consumers, matching the unidirectional layering guarded bytest/architecture/import_flow_test.dart. - Root
flutter analyzeno longer crawlspackages/**; the sibling package owns its own analyze step with its ownanalysis_options.yaml. Previously, CI failed on root analyze because the sibling'spub gethadn't run before root analyze.
Breaking changes #
RuneContextconstructor now requires asourcenamed parameter (String, non-nullable). External code constructingRuneContextdirectly (e.g. custom test harnesses, alternative views) must supply the source string that the AST originates from, or''if the context is used in a path where diagnostics aren't needed. Callers going throughRuneView/RuneConfigare unaffected; the production path threadswidget.sourceautomatically.
0.1.0 - 2026-04-18 - Phase 4 #
Added #
benchmark/parse_resolve_bench.dart: runnable Dart script that measures parse + resolve time on a canonical ~30-node widget tree over 500 iterations. Reports cold (cache-miss) and warm (cache-hit) stats; soft-checks cold p95 against a 16ms / 60fps budget.RuneDevOverlay: opt-inStatelessWidgetwrapper that, on long-press in debug/profile builds, opens a bottom sheet with the source string and a descendant count. Pass-through in release builds. Exported frompackage:rune/rune.dart.
Changed #
RuneViewnow overridesState.reassemble()to clear its per- instanceAstCache. Hot-reload picks up source edits in the host app immediately; previously the cached parsed AST would continue to serve.
Released #
- First minor release. API stable enough for early adopters. Future 0.x minors focus on widget / value / extension additions without breaking existing consumers.
0.0.10 - 2026-04-18 - Phase 3c #
Added #
- Sibling package
rune_responsive_sizeratpackages/rune_responsive_sizer/: aRuneBridgeimplementation that registers four responsive-sizing property extensions:.w(percent of screen width),.h(percent of screen height),.sp(text-scaled pixels),.dm(percent ofmin(width, height)). Applied viaRuneConfig.defaults().withBridges([const ResponsiveSizerBridge()]). Independent version track; ships at0.0.1alongside this root bump.
0.0.9 - 2026-04-18 - Phase 3b #
Added #
- Deep dot-path data access:
user.profile.nameand arbitrary-depth traversal now work throughPropertyResolver's new map-first branch. EachPropertyAccesssegment walks one map level; missing keys returnnull. - Index access:
items[0],map['key'],items[0].titlevia a newIndexExpressiondispatch arm. List out-of-range throwsResolveException; non-list/map targets throw with a type-mismatch message. for-element in list literals:[for (final item in items) Text(item.title)]. Loop variable binds into a scopedRuneDataContextviaextend, soitem.titleresolves against the merged data. Static elements around the for-element are preserved in source order; nested for-elements compose. Onlyfor-each with declaration is supported (C-styleforandIfElementthrowResolveException).
Changed #
PropertyResolver.resolve: when the target is aMap<String, Object?>, the map value wins over any same-named extension. Data beats extensions on conflict (matches the data-first rule established byIdentifierResolver.resolvePrefixedin Phase 3a).
0.0.8 - 2026-04-18 - Phase 3a #
Added #
ExtensionRegistry: property-name-keyed registry of(target, ctx) => Object?handlers. Used by the newPropertyResolverto evaluate receiver-style property access like10.px,(5).doubled.RuneBridge: single-method contract (void registerInto(RuneConfig)) that third-party packages implement to bundle widget/value/constant/ extension contributions. Applied viaRuneConfig.withBridges([...]).PropertyResolver: dispatcher arm forPropertyAccessAST nodes; resolves target via the expression resolver then delegates toctx.extensions.RuneContext.extensionsfield (required);RuneConfig.extensionsfield +withBridges(List<RuneBridge>)fluent method.- Architecture-test rule gating the new
src/bridges/layer.
Changed #
IdentifierResolver.resolvePrefixednow checksctx.databeforectx.constants.user.name(whereuseris aMapin data) resolves todata['user']['name'];Colors.redstill falls through to the constants registry. Non-Mapdata values at the prefix raiseResolveExceptionwith a type-mismatch message.
0.0.7 - 2026-04-18 #
0.0.6 - 2026-04-18 - Phase 2e #
Added #
RuneDefaultsabstract-final helper class with four static entry points:registerAll,registerWidgets,registerValues,registerConstants. Enables cherry-picking defaults into customRuneConfigs.- Architecture test (
test/architecture/import_flow_test.dart) that walkslib/src/**/*.dartand guards the unidirectional layer import hierarchy (binding is self-contained, parser only reads core, nothing importsdynamic_view.dart, etc.).
Changed #
RuneConfig.defaults()now delegates toRuneDefaults.registerAll(public behavior unchanged; twenty-plus builder imports moved out ofconfig.dart).pubspec.yamlversion bumped to0.0.6.
0.0.5 - 2026-04-18 - Phase 2d #
Added #
- Three button widget builders:
ElevatedButton,TextButton,IconButton. Each translates aStringonPressedsource argument into aVoidCallbackthat invokesctx.events.dispatch(eventName). RuneEventDispatcher.setCatchAllHandler: catch-all bridge invoked on every dispatch in addition to any named handler.RuneView._buildContextnow installswidget.onEventas the dispatcher's catch-all when non-null, closing the gap that left source-declared events unobservable.
0.0.4 - 2026-04-18 - Phase 2c #
Added #
- Ten widget builders:
Padding,Center,Stack,Expanded,Flexible,Card,Icon,ListView,AppBar,Scaffold. - Two
Imagevalue builders (Image.network,Image.asset), registered viaValueRegistryso they can coexist under the sharedtypeName == 'Image'and disambiguate on constructor name. FlexFitenum seeded into the Phase 2a constants module.- Phase 2c icons seed (
phase_2c_icons.dart): ~60 commonIcons.*constants.
0.0.3 - 2026-04-18 - Phase 2b #
Added #
- Seven value builders:
EdgeInsets.symmetric,EdgeInsets.only,EdgeInsets.fromLTRB,Color(hex),TextStyle,BorderRadius.circular,BoxDecoration. BoxShapeenum seeded into constants registry.
Fixed #
ContainerBuilderwas silently dropping thedecorationnamed argument. Discovered via Phase 2b integration tests and fixed as part of the same phase.
0.0.2 - 2026-04-18 - Phase 2a #
Added #
ConstantRegistry: two-leveltypeName.memberNamekeyed store (independent of the genericRegistry<T>base because the two-level shape lets error messages cite both halves).IdentifierResolver: handlesSimpleIdentifier(data lookup inRuneDataContext) andPrefixedIdentifier(constants lookup).ExpressionResolverdispatcher extensions forSetOrMapLiteral,StringInterpolation, andAdjacentStrings.- Phase 2a constants seed: all of
Colors.*,MainAxisAlignment,CrossAxisAlignment,MainAxisSize,TextAlign,TextOverflow,Alignmentsingletons,BoxFit,StackFit,Axis,FontWeight,EdgeInsets.zero. RuneContextgrew a requiredconstantsfield.RuneConfig.defaults()now seeds the Phase 2a constants.
Changed #
RuneDataContextandRuneEventDispatcherrenamed from their unprefixed forms (DataContext/EventDispatcher) to avoid a collision withflutter_test's ownEventDispatcherand align with theRune*public-surface convention.RuneEventDispatcher.dispatchnow catches handler exceptions and logs viadebugPrint, so arity mismatches or handler throws do not escape into the render pipeline.
0.0.1 - 2026-04-18 - Phase 1 MVP #
Added #
- Core:
sealed class RuneExceptionwith five variants (ParseException,ResolveException,UnregisteredBuilderException,ArgumentException,BindingException). - Registry: generic
Registry<T>,WidgetRegistry,ValueRegistry. - Parser:
DartParser(wrapsanalyzer.parseStringwith thedynamic __rune__ = $source;trick to parse bare expressions) and LRUAstCache. - Builder contracts:
RuneBuilder,RuneWidgetBuilder,RuneValueBuilder;ResolvedArgumentswith type-safe accessors. - Resolver:
LiteralResolver,ExpressionResolver(pattern-match dispatcher with late-bound invocation resolver),InvocationResolverhandling bothMethodInvocation(bare call syntax, the primary user-facing shape) andInstanceCreationExpression(withnew). - Five widget builders:
Text,SizedBox,Container,Column,Row. - One value builder:
EdgeInsets.all. RuneConfig.defaults()factory wiring Phase 1 builders.RuneViewpublicStatefulWidget: parses, caches (LRU), resolves, renders;onErrorcallback +fallbackwidget for failures.- Example app at
example/lib/main.dartdemonstrating the full Phase 1 feature set.