v0.0.46

5 July 2026

FieldTypeCollection and Item for registry-backed collection fields, {fragment} and {companion} Latte tags, and hidden/status/locked moved to the Record base class.

Added

  • FieldTypeCollection : fieldtype for ordered, registry-backed collections of Item objects. Scoped to a page UUID and field name. decode() returns a RecordCollection hydrated from the registry; callable with a query DSL string to filter/sort inline ($page->items('sort=-created,limit=5')).
  • Item : new Record subclass for collection items. Items live at {page_path}/_{field_name}/{shard}/{uuid}/ (sharded by the first two characters of the UUID). Its template derives from the owning field definition's options.fields. registryFields() includes parent (owning page UUID) and field (field name).
  • RecordCollection::withContext(string $parent, string $field) : returns a new collection carrying parent UUID and field name context, required for item creation and for FieldTypeCollection hydration.
  • RecordCollection::new(array $data) : creates a new Item in the collection, writes its index.json, calls save($data), and appends it to the collection. Requires a collection that has been given context via withContext().
  • {fragment 'name'}...{/fragment} : Latte tag that declares a named, self-targeting fragment. Compiles to {block name}{partial 'name'}...{/partial}{/block}. Accepts optional target: (override the HTMX target id) and swap: (override the swap strategy) arguments.
  • {companion 'name'}...{/companion} : Latte tag that registers a block reachable only via its fragment URL (:name). Never renders as part of a normal page load : if (false) wrapper ensures it's compiled into the template but never emitted inline.
  • Registry schema 8: adds locked, parent, and field columns; parent+field composite index; scanItems() scans _{field} subdirectories of page folders for item records. allRows(string $query) : unbounded variant of rows() for internal collection hydration.

Changed

  • hidden, status, and locked moved from Page up to Record, where they are now initialized from the $row array when provided : items hydrated from the registry skip redundant file reads for these values. Record::registryFields() and Record::structuralKeys() now include all three at the base level.
  • Page::structuralKeys() returns only ['view']; User::structuralKeys() returns [] : both rely on Record's base for hidden/status/locked.