v0.0.42
4 July 2026
Named-parameter callable dispatch, per-page session scoping, config-driven addon load order, TemplateFields base+type merge, and functions.php cleanup.
Added
Invoke::call(callable $fn, array $available): calls a callable by matching$availablevalues to its parameters by name, in whatever order the callable declares them, skipping any it doesn't ask for. Controllers, routers, and hooks can now declare only the parameters they need, in any order.Page::session(): returns a session scope auto-namespaced to the page's view name.AddonPageoverrides this to scope to the addon name (the part of the id after/), so all of an addon's views share one scope.TemplateFields::values(App $app, array $defs, array $current, string $path): static helper that builds aFieldValueSetfrom resolved defs and current values. Shared betweenField::optionFields()andFieldValue::optionFields()to avoid duplicating the build loop.FieldTypeNumber::encode(): coerces the saved value to numeric; empty string saves asnullinstead of being stored as-is.
Changed
- Addon loading is now config-driven: addons load in the order they appear in
config.json addons, with no filesystem directory scan. Config order is the only load-order guarantee. TemplateFields::resolve()now merges the base template (fieldtypes/index.json) with the type-specific template (fieldtypes/{type}/index.json) : fields from the base apply to every type unless the type template overrides them.Record::templateFieldDefs()renamed toRecord::templateFields().App::send()now accepts an optional?string $blockparameter : honoring the:blockURL suffix the same way a normal GET does.DirectSessionAccessRuleextended to also checkscope()andmake()call sites against configured allowed file paths, enforcing scope ownership by filename.functions.php:q()aliased to a newquery(),p()aliased to a newpage(),find()added as a shorthand forpage('/')->find($query).csrf(),isLoggedIn(), andfieldtypeEditTemplate()removed.