feat: page builder — engine (L1) + editor backend (L2a-1) + editor frontend (L2a-2)#232
feat: page builder — engine (L1) + editor backend (L2a-1) + editor frontend (L2a-2)#232bambamboole wants to merge 38 commits into
Conversation
Bundle ReportChanges will increase total bundle size by 48.96kB (2.74%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: lattice-workbench-esmAssets Changed:
Files in
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
8a4f5a7 to
cee9cfa
Compare
Wires a workbench form (BlockPageForm) with a BlockEditor field behind two demo blocks (HeroBlock, ColumnsBlock) at /block-editor, and adds a Pest browser test that adds a block, selects it, edits its title, verifies the canvas preview updates on blur, and confirms the submission persists.
The wire prop carried the raw config value ('lattice/blocks/render'), which
the browser resolves relative to the current page URL — previews 404 on any
nested route. Normalize with a leading slash, matching DefinitionRegistry::endpointFor().
DefinitionRegistry::keyFor already validates the definition subclass and extracts the attribute key; expose it instead of re-implementing the attribute read in BlockEditor, and hoist the BlockRenderer container resolution out of the per-row map.
Hand-building the bracket syntax from the dot path breaks the submitted name once the editor sits inside a field scope; toHtmlName(appendPath()) matches how the sibling type inputs are named.
The component inherited Builder's full prop surface but ignored it: visibleWhen/requiredWhen conditions no-opped, validation errors never displayed, and append ignored maxItems. Wire the same hooks BuilderComponent uses.
Blur bubbles from every child, so tabbing between inspector fields fired a render request per focus move. Commit only when focus leaves the inspector, and track each block's last-rendered source (type + attributes, seeded from the server payload) so unchanged commits skip the request — the same change-aware principle precognitive validation follows. Failed renders keep the previous signature and retry on the next commit.
Drag handle, empty-inspector hint, and unknown-block note were hardcoded English; route them through useT with en/de entries like the sibling field components.
…e facade Blocks are headed for a first-party package, so core must not hardwire BlockRegistry into LatticeRegistry/the facade. Apps register explicit blocks via app(BlockRegistry::class)->register() (or rely on AsBlock discovery); the extracted package will own any registration sugar.
863fc6b to
a5b3fd5
Compare
- BlockEditor extends TypedRowsField: blocks() builds RowTemplates from the AsBlock classes, and the table/row-action chrome props it never honored drop off its wire type. - The client reads the templates wire key via rowTemplatesOf, submits type + rowId per row through the shared RowKeyInputs, and slot rows keep their rowId on submit (hiddenInputsFor no longer strips it). - Browser tests select block shells positionally since row ids are uuids now; the type in:-rule assertion follows Rule::in.
a5b3fd5 to
ac8d8bd
Compare
Gutenberg-style page builder for Lattice, built in layers. Draft.
Design + plans in
docs/superpowers/(gitignored, local).Layer 1 — engine core
A block = a discoverable
#[AsBlock]class with an attribute schema (Fields) +render(FormData, BlockSlots): PageSchema.AsBlock/blocksdiscovery,BlockDefinition,BlockRegistry,BlockRenderer(unknown → placeholder, restricted-nesting slots),AsBlockscast +HasBlockstrait. Renders in-app via the existing wire→React registry; no prescribed Page model.Layer 2a-1 — editor backend
BlockEditorform field (extendsBuilder,field.block-editor);->blocks([HeroBlock::class])builds inspector templates from#[AsBlock]classes; serializes each stored row's rendered wire.POST lattice/blocks/renderendpoint for on-blur live preview (allowed-blocks scoped, validated, unsigned-rejected).Layer 2a-2 — editor frontend
A
field.block-editorReact editor:Correctness fixes made during review
move-blocktree move hardened against silent drops on nested moves.Builder/HandlesRowSchemas: optional row fields lacking->rules()are now retained throughvalidate()(previously dropped by Laravel'sexcludeUnvalidatedArrayKeys).Still to come
sortableKeyboardCoordinatesrefinement.Tests / gates
Green:
composer check(1020 PHP tests) andnpm run check(971 Vitest +build:lib), plus a Pest v4 browser suite covering add / select / edit / on-blur preview / multi-block save.🤖 Generated with Claude Code