v0.0.37
29 June 2026
View becomes a Record, type-level template fallback, field definitions are now first-class records, git revision-addressed page URLs, block-targeted rendering, and a new UI component library.
Added
App::loadType(string $base, string $name, string $baseClass): general resolver for "a type's own folder can override its base class" (site overrides phlat): looks for{base}/{name}/{name}.php, requires it if present, then checks$baseClass::boundClass($name). Used byApp::fieldtype()and nowApp::page(), which binds a request to aPagesubclass by view.Record::type()(renamed fromtemplateKey()) andRecord::template(): everyRecordsubtype now declares its own root discriminator (Page's view,User's role,Field's fieldtype name) and resolves a matchingindex.jsonviaRecord::resolveTemplate(string $base)({site|phlat}/{base}/{type}/index.json, falling back to{base}/index.json).Record::templateFieldDefs()replacesPage's oldviewFieldDefs()for any subtype. New built-in templates:phlat/fieldtypes/index.json(global meta-fields:required,help,placeholder,min_length,max_length) andphlat/roles/index.json(per-role field requirements).Record::keyFor(string $type, string $value): builds registry uuid keys ("page:{uuid}","field:{name}", etc.), replacing ad-hoc string interpolation across the registry,FileStore,Session, and therelationfieldtype.Page::registryKeyFor(string $uuid)is the page-specific shorthand.View extends Record: a view is now a resolvedindex.json(title, description) with full registry/uuid plumbing, not a bare renderer.View::render(array $data, ?string $block = null)can target a single Latte block, wrapping it in@ui/fragment.latteso a direct browser hit still gets the full shell while an HTMX request gets just the block.- Block-targeted rendering end to end :
Requestparses a trailing:{block}suffix on the request's last path segment (alongside the existing~{revision}suffix) intoRequest::$block, threaded throughApp::execute()→Page::render()→View::render(). - Git revision-addressed page URLs :
phlat/addons/git/PageHistory::resolveRevision()/pageAt()and a newPageRevision extends Pageresolve a~{n}(or ref) suffix to a read-only page whosedata()/index()reflect historical content via the newGit::readFile(path, ref, file)(git show {ref}:./{file}). Fielddefinitions are now fullRecords :App::field()returns a?Field(constructedisDefinition: true) instead of a raw array, with registry indexing and afield.update:afterevent.Field::save()persistslabel/type/optionsas structural keys and bustsApp::forgetField()'s cache.Registry::scanFields()indexes every resolved field definition, queryable via a newFieldDefinitionCollection;FieldIndex::index()now accepts anyRecord, indexing a field'stypeand flatoptions.Page::tail()(alias forremainder()) andPage::target(): resolvesremainder()straight to aPage, replacing the common$app->page($page->subPath())pattern.- New UI component library at
phlat/addons/ui/views/components/:button,card,alert,hero,menu,pagination,stats,table,tabs,json-node,preview-section, plus aform/set (input,textarea,checkbox) : each parameter-driven via{default ...}.components/index.latteis a live preview page for all of them. - New admin field-editing views :
fields/edit.latte,fields/list.latte,fields/new.latte(split out of one monolithic template), and a sharedui/field-row.latterow component reused across page/view/field/browse admin screens.
Changed
Request::param()and thekey:valuepath-param-segment convention removed : only the request's last segment carries a suffix now (:{block}or~{revision}).Page::subPath()renamed toPage::remainder().App::field(string $name): ?array→?Field.App::fieldtype()now goes throughApp::loadType()instead of its own resolution loop.Registryschema bumped to5(adds thefieldrecord type, re-keys uuids viaRecord::keyFor()).phlat/addons/admin/views/fields/reworked to load/save throughApp::field()/Field::save()instead of hand-rolled directory scans and rawindex.jsonreads/writes.- Admin views renamed for consistency :
page/page-edit.latte→page/edit.latte,views/view-edit.latte→views/edit.latte. Admin layout's page-tree sidebar moved out of the shared shell intobrowse/index.lattespecifically; nav search box and view/field links reflowed into the overflow dropdown. accordion.latterewritten from static hardcoded markup to a data-driven{foreach $items}component with$name/$style/$joined/$independentoptions.
Removed
- Dead in-memory
RecordCollection::find()DSL : confirmed fully gone, no remaining references.