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 by App::fieldtype() and now App::page(), which binds a request to a Page subclass by view.
  • Record::type() (renamed from templateKey()) and Record::template() : every Record subtype now declares its own root discriminator (Page's view, User's role, Field's fieldtype name) and resolves a matching index.json via Record::resolveTemplate(string $base) ({site|phlat}/{base}/{type}/index.json, falling back to {base}/index.json). Record::templateFieldDefs() replaces Page's old viewFieldDefs() for any subtype. New built-in templates: phlat/fieldtypes/index.json (global meta-fields: required, help, placeholder, min_length, max_length) and phlat/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 the relation fieldtype. Page::registryKeyFor(string $uuid) is the page-specific shorthand.
  • View extends Record : a view is now a resolved index.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.latte so a direct browser hit still gets the full shell while an HTMX request gets just the block.
  • Block-targeted rendering end to end : Request parses a trailing :{block} suffix on the request's last path segment (alongside the existing ~{revision} suffix) into Request::$block, threaded through App::execute()Page::render()View::render().
  • Git revision-addressed page URLs : phlat/addons/git/PageHistory::resolveRevision()/pageAt() and a new PageRevision extends Page resolve a ~{n} (or ref) suffix to a read-only page whose data()/index() reflect historical content via the new Git::readFile(path, ref, file) (git show {ref}:./{file}).
  • Field definitions are now full Records : App::field() returns a ?Field (constructed isDefinition: true) instead of a raw array, with registry indexing and a field.update:after event. Field::save() persists label/type/options as structural keys and busts App::forgetField()'s cache. Registry::scanFields() indexes every resolved field definition, queryable via a new FieldDefinitionCollection; FieldIndex::index() now accepts any Record, indexing a field's type and flat options.
  • Page::tail() (alias for remainder()) and Page::target() : resolves remainder() straight to a Page, 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 a form/ set (input, textarea, checkbox) : each parameter-driven via {default ...}. components/index.latte is 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 shared ui/field-row.latte row component reused across page/view/field/browse admin screens.

Changed

  • Request::param() and the key:value path-param-segment convention removed : only the request's last segment carries a suffix now (:{block} or ~{revision}). Page::subPath() renamed to Page::remainder().
  • App::field(string $name): ?array?Field. App::fieldtype() now goes through App::loadType() instead of its own resolution loop.
  • Registry schema bumped to 5 (adds the field record type, re-keys uuids via Record::keyFor()).
  • phlat/addons/admin/views/fields/ reworked to load/save through App::field()/Field::save() instead of hand-rolled directory scans and raw index.json reads/writes.
  • Admin views renamed for consistency : page/page-edit.lattepage/edit.latte, views/view-edit.latteviews/edit.latte. Admin layout's page-tree sidebar moved out of the shared shell into browse/index.latte specifically; nav search box and view/field links reflowed into the overflow dropdown.
  • accordion.latte rewritten from static hardcoded markup to a data-driven {foreach $items} component with $name/$style/$joined/$independent options.

Removed

  • Dead in-memory RecordCollection::find() DSL : confirmed fully gone, no remaining references.