Layout Builder — Improvement & Growth Plan
Package: capell-app/layout-builder · Kind: package · Tier: free · Product group: Capell Foundation · Bundle: foundation · Status: Complete
1. Snapshot
Section titled “1. Snapshot”Layout Builder is the composition engine for Capell: it persists named layout containers and reusable Widget records, and resolves them into a query-free public graph for the frontend. It spans three surfaces (admin, frontend, console) and ships 48 Actions (src/Actions/), the bulk of the domain logic — public rendering through BuildPublicLayoutGraphAction (src/Actions/BuildPublicLayoutGraphAction.php, behind the PublicLayoutGraphBuilder contract), a Livewire dual-mode editor (src/Livewire/Filament/LayoutBuilder.php + four Concerns/), layout health diagnostics (AnalyzeLayoutHealthAction), reusable presets (LayoutPreset model + SaveLayoutPresetAction/ApplyLayoutPresetAction), encrypted lazy fragments (Actions/Fragments/RenderPublicFragmentAction.php), and a deterministic widget visual-regression command. Tables: layouts (JSON containers), widgets, widget_assets, widget_widgets, layout_presets (database/migrations/2026_05_10_190841_*). It is consumed by the Frontend package (via the PublicLayoutGraphBuilder / FrontendRuntimeManifestContributor / WidgetResourceUsageContributor tags), themes (LayoutAreaRegistry, e.g. Foundation Theme’s header area), block-library (BlockRegistry for diagnostics), and frontend-authoring (editable-region surface). Marketplace summary (verbatim): “Visual layout composition, content-first editing, and public layout rendering for Capell.” Manifest currently promotes only the extension card after visual audit showed the committed docs/screenshots/* captures are broken by a huge black arc/default shell and do not prove a styled Capell surface.
2. Improvements (existing functionality)
Section titled “2. Improvements (existing functionality)”Prioritized.
-
Implement a real package health check —
compatibleCapellApiVersion()is the only method on the class; the manifest advertises the check as “package surfaces, providers, and install health are discoverable by Diagnostics” withseverity: critical, but nothing verifies tables exist, the install action ran, the public graph builder is bound, or the editor Livewire component is registered. A critical-severity check that only echoes'^4.0'gives false assurance. —src/Health/LayoutBuilderHealthCheck.php— S -
Delete or wire up
CapellLayoutCacheKeyEnum— the enum definesWidgetByKeyandWidgetOptionscache key prefixes, but a full-tree grep finds zero references outside the enum file itself (noremember/forgetagainst these keys anywhere insrc/,resources/,tests/). Either dead code or an abandoned caching scheme. The live caching path isLayoutLoaderviaCapellCore::rememberCache+ a request-scoped$preloadedarray. —src/Enums/CapellLayoutCacheKeyEnum.php,src/Support/Loader/LayoutLoader.php— S -
Align
capell.jsondependencies withcomposer.json— manifestdependencies.requireslists onlycore+frontend, but composer requiresadminandblock-librarytoo (and the code hard-importsCapell\BlockLibrary\Support\BlockRegistry,Capell\Admin\…). The package’s owndocs/overview.md“Known Risks” already flags this as a pre-publication blocker. Catalog resolution / install ordering will be wrong without it. —capell.json(dependencies.requires) — S -
Done/Shipped: Livewire notifications are translated. Notification titles/bodies in the editor and related Filament helpers now resolve through translation keys rather than inline English, with duplicate preset/save failures using
capell-layout-builder::message.*copy. A static guard prevents literaltitle()/body()notification strings and raw exception messages from being surfaced to editors. —src/Livewire/Filament/LayoutBuilder.php,src/Livewire/Filament/Support/LayoutBuilderActionFactory.php,tests/Unit/LayoutBuilderNotificationTranslationTest.php— M -
Partially shipped: recaptured and promoted styled admin/editor marketplace screenshots — the broken default-shell/admin-dashboard captures were replaced with real runner-backed Capell admin PNGs for the widgets index, create widget form, page editor, and layouts index, with light/dark pairs promoted in
marketplace.screenshots[]. The frontend render target remains open because the prepared app still renders the oversized black wave/artifact called out in the screenshot-quality audit; keep frontend PNGs as runner evidence only until the public theme capture is visually clean. —capell.json(marketplace.screenshots),docs/screenshots.json— S -
Split the god-Action
BuildLayoutContentInventoryAction(554 lines) — by far the largest Action and the backbone of content-first mode. At this size it is hard to test in isolation and hard to extend. Extract container/asset traversal and the per-widget projection into focused collaborators. —src/Actions/BuildLayoutContentInventoryAction.php— M -
Done/Shipped: orphan/uncovered Actions audited. The behaviorless
ApplyLayoutPlanActionwas removed. The apparent orphan list is otherwise reachable or covered:PreviewLayoutPlanActionis the AI/preset preview seam and has preset-match coverage,SaveFormComponentRelationshipActionhas relationship save/delete coverage,PersistLayoutBuilderStateActionis the editor save path, andInstallPackageActionremains a public compatibility installer consumed by Blog/Demo Kit setup helpers with package-local coverage now pinning its widget-type/layout setup behavior.src/Actions/ApplyLayoutPlanAction.php,src/Actions/InstallPackageAction.php,tests/Unit/LayoutBuilderScaffoldingAndRelationCoverageTest.php— M -
Move undo/redo from in-memory to durable —
LayoutMutationHistoryandPushLayoutMutationSnapshotActionkeep history in Livewire component state (MAX_HISTORY_DEPTH = 20), so it is lost on navigation/refresh and bloats the wire payload (full state snapshot per step). Consider trimming snapshots to diffs and/or persisting to the layout’sadminJSON for cross-session undo. —src/Support/LayoutMutationHistory.php,src/Actions/Mutations/PushLayoutMutationSnapshotAction.php— M
3. Missing Features (gaps)
Section titled “3. Missing Features (gaps)”Capabilities declared: layout-builder, layout-builder-admin, layout-builder-content-first, layout-builder-public-rendering. All four are genuinely reachable in production. Gaps against page-builder norms:
- Done/Shipped: layout templates / starter layouts (differentiator). Registered layout presets can now be materialized into a complete builder state through
ApplyStarterLayoutPresetAction, exposed through a translated Livewire starter-gallery UI in the editor, and applied to replace the full builder state. Package-local Action, registry, render, and Livewire mutation coverage pins the current foundation scope. Page-creation shortcuts and paid Layout Pro packaging remain future product depth above this free engine. — table-stakes-plus. - Global / synced blocks (differentiator).
Widgetrecords are reusable andwidget_widgetsnests them, but editing one instance does not propagate to all placements (no “edit once, update everywhere”). This is a headline feature in WordPress Gutenberg / Webflow and a strong paid upsell. — differentiator. - Revision history / restore (table-stakes). No layout versioning.
LayoutMutationHistoryis per-session undo only; there is no “restore previous published layout.” Page builders are expected to have this. — table-stakes. - Responsive controls beyond width.
LayoutBreakpoint(Desktop/Tablet/Mobile) drives per-breakpointcolspanoverrides only (ResizeLayoutContainerAction). No per-breakpoint visibility on containers (device visibility exists for widgets viapresentation.manage_advanced), ordering, or spacing. — table-stakes. - Accessibility tooling.
AnalyzeLayoutHealthActionchecks duplicate anchors, too-many-cards, and unsupported variants — but no a11y diagnostics (heading-order, alt-text presence, contrast hints).HeroWidgetHasPrimaryHeadingActionhints the appetite exists; generalize it. — differentiator. - Drag-drop nested columns. Containers are a flat keyed map with
colspan; there is no true nested column/row tree in the layout model (nesting is only widget-in-widget viawidget_widgets). Deep section→row→column composition is a common expectation. — table-stakes. - Block/widget reuse discovery.
FindReusableWidgetsActionexists and is wired into the editor (ManagesLayoutBuilderState), but it is a thin (20-line) lookup — no usage counts, no “where used” surfaced to editors at insert time. — incremental.
4. Issues / Risks
Section titled “4. Issues / Risks”- Stub critical health check (false signal). As §2.1 —
severity: criticalhealth entry backed by a no-op.src/Health/LayoutBuilderHealthCheck.php. - Dead cache enum. As §2.2 — unused
CapellLayoutCacheKeyEnum; risk of a future dev wiring against an abandoned key scheme.src/Enums/CapellLayoutCacheKeyEnum.php. - Done/Shipped: public render performance budgets are covered with deterministic suite guards.
PublicLayoutGraphStressTestnow asserts the manifest-driven 50-query graph budget, a zero-query hydrated Blade render, high-volume query scaling, and public metadata leak guards. The former hard 20ms wall-clock assertion was removed because it was not stable under parallel Pest host load; the package manifest still documents the target budget. Normal Blade widgets also avoid opaque fragment-reference encryption unless a lazy fragment, Livewire widget, or fragment interaction needs it.tests/Integration/PublicLayoutGraphStressTest.php,resources/views/components/layout/widget.blade.php. - Cache invalidation correctness unverifiable here.
performance.cacheSafety.invalidationSourcesis[]andcacheable: false, yetqueueInvalidation: trueandcacheTags: ["layout-builder"]are set, andLayoutLoaderdoes cache theLayoutviaCapellCore::rememberCache. NoCacheInvalidationRegistry::registerDependency()call exists in the package — invalidation is delegated entirely to core viaLayoutLoaded/AfterRecordSavedlisteners and the emptyinvalidationSourcesis undocumented. Either register the dependency explicitly or document why core owns it.src/Listeners/,src/Support/Loader/LayoutLoader.php. - Public-output safety is tested but narrow.
LayoutBuilderPublicRenderingSafetyTest.phpasserts the rendered HTML excludeslayoutDiagnostics,LayoutFragmentData,pageable_type,widget_key,responsive,colspan,signed editor— good. ButPublicLayoutWidgetDatacarrieskey(the widget key) and?string $html; the safety net depends on the Blade layer never echoingdata.key/container meta. Lazy fragments are encrypted+revalidated (perdocs/overview.md) but there is no test asserting a tampered/replayed_fragments/{reference}returns a generic 404.src/Data/PublicLayoutWidgetData.php,src/Actions/Fragments/RenderPublicFragmentAction.php. - Done/Shipped: install/persist paths are covered.
InstallPackageActionhas compatibility coverage for consuming package setup helpers, andPersistLayoutBuilderStateActionnow has direct coverage proving container persistence, page layout assignment, widget-asset callback execution, preview invalidation, and transaction rollback when widget-asset persistence fails.src/Actions/PersistLayoutBuilderStateAction.php,src/Actions/InstallPackageAction.php,tests/Unit/PersistLayoutBuilderStateActionTest.php,tests/Unit/LayoutBuilderScaffoldingAndRelationCoverageTest.php. - Preview-image side effects on read.
GenerateLayoutPreviewImageActionwrites toStorage::disk('public')andsaveQuietly()s the layout; ensure this is never triggered on a public request path (it is queued viaAsJob, butGetLayoutPreviewImageUrlActionreadsadminJSON — keep admin-only).src/Actions/GenerateLayoutPreviewImageAction.php. - i18n debt. Inline notification strings (§2.4) and
Str::headline()fallbacks inAnalyzeLayoutHealthAction::variantLabel()mean some editor-facing text bypasses translation.src/Livewire/Filament/LayoutBuilder.php,src/Actions/AnalyzeLayoutHealthAction.php.
5. Marketplace & Positioning
Section titled “5. Marketplace & Positioning”This is correctly a free / foundation package — it is the composition engine the rest of the platform renders through (Frontend, themes, block-library all depend on it). Standalone pricing makes no sense; its commercial value is as the floor that makes Capell a real page-builder CMS rather than a flat-content CMS.
Current copy — critique. Manifest summary and composer description are identical and feature-listy (“Visual layout composition, content-first editing, and public layout rendering”) — three internal capability names, no benefit, no audience. It tells a buyer what the code does, not what they get.
Improved summary (manifest):
“Compose pages visually with reusable widgets and named layout areas — edit content fast in content-first mode, or drag-and-drop the full layout. Renders to clean, query-free public HTML that never leaks editor internals.”
Improved composer description (one line):
“The visual layout and widget composition engine for Capell CMS — content-first and drag-drop editing, responsive containers, reusable widgets, and safe public rendering.”
Tier / upsell. Keep the engine free. The credible premium line sits on top of existing primitives: (a) whole-page templates / starter-layout gallery (extends LayoutPreset), (b) global synced blocks (edit-once-update-everywhere over Widget/widget_widgets), (c) advanced blocks & layout revision history. Package these as a paid “Layout Pro” add-on so the foundation stays open and the differentiators monetize. The advanced presentation controls already gated behind presentation.manage_advanced show this split is intended.
Media gaps. Recapture the 10 screenshot targets with the real Capell runner assets/CSS loaded, then promote the strongest 3–5. The current committed PNGs stay as broken-runner evidence only and should not be buyer-facing marketplace media. Add a short animated capture of content-first editing and responsive-breakpoint switching — the two demos docs/overview.md explicitly says should be captured separately. A GIF of the editor is worth more than any static card for a layout product.
Pitch contribution. Strengthen the Capell pitch by leading with this package: it is the proof that Capell is a composable CMS. Position it as “the engine themes and the public site render through,” then point buyers to the Pro add-on for templates/global blocks.
Keywords / tags (8–12): page-builder, visual-editor, drag-and-drop, layout, widgets, content-first, reusable-blocks, responsive-layout, filament, livewire, cms, block-composition.
6. Prioritized Roadmap
Section titled “6. Prioritized Roadmap”| Item | Bucket | Effort | Impact | Section ref |
|---|---|---|---|---|
Align capell.json deps with composer (add admin, block-library) | Done | S | High | §2.3 / §4 — closed 2026-06-05: manifest requirements now include core, admin, block-library, and frontend. |
| Implement real package health check (tables, install, bindings) | Done | S | High | §2.1 / §4 — closed 2026-06-05: LayoutBuilderHealthCheck now probes storage tables, public graph builder binding, and editor Livewire registration. |
Remove dead CapellLayoutCacheKeyEnum (or wire it) | Done | S | Med | §2.2 / §4 — closed 2026-06-05: the unused cache enum was removed instead of preserving an abandoned key scheme. |
| Recapture and promote 3–5 styled screenshots into manifest marketplace block | Later | S | High | §2.5 / §5 — current implementation scope is closed: the persistent prepared app recaptured and promoted real light/dark Capell admin/editor PNGs for widgets index, create widget form, page editor, and layouts index. Keep the frontend render target unpromoted until the public theme capture no longer shows the oversized black wave/artifact. |
Rewrite manifest summary + composer description | Done | S | High | §5 — closed 2026-06-05: manifest/composer copy now explains visual composition, content-first editing, and safe query-free public HTML. |
Done/Shipped: Add public render performance-budget test coverage. Evidence: PublicLayoutGraphStressTest covers manifest-driven query budgets, zero-query hydrated Blade rendering, high-volume query scaling, and leak guards; wall-clock budget remains manifest documentation rather than a parallel-suite assertion. | Done | M | High | §4 |
| Add fragment tamper/replay → 404 safety test | Done | M | High | §4 — closed 2026-06-05: PublicFragmentRenderingTest covers invalid encrypted references, replayed references for another site/page, route 404s, and unsafe authoring HTML rejection. |
| Done/Shipped: Audit & remove/cover orphan Actions (ApplyLayoutPlan, PreviewLayoutPlan, SaveFormComponentRelationship, InstallPackage) | Done | M | Med | §2.7 / §4 — removed no-op ApplyLayoutPlanAction; confirmed/covered the reachable preview, relationship, persist, and compatibility install paths. |
| Cover PersistLayoutBuilderStateAction + install path | Done | M | High | §4 |
| Translate inline Livewire notifications | Done | M | Med | §2.4 / §4 |
| Split BuildLayoutContentInventoryAction (554 lines) | Done | M | Med | §2.6 — closed 2026-06-07: the Action is now a coordinator, with item projection/labels in LayoutContentInventoryItemFactory and group assembly in LayoutContentInventoryGrouper; package-local inventory tests preserve behavior. |
| Whole-page templates / starter-layout gallery (Pro) | Done | L | High | §3 / §5 — closed 2026-06-08: registered starter presets can replace the full builder state via ApplyStarterLayoutPresetAction and LayoutBuilder::applyStarterLayoutPreset(), and the editor now renders a translated starter-gallery UI backed by ListLayoutPresetsAction. Package-local Action, registry, render, and Livewire mutation tests pass. Future page-creation shortcuts and paid Layout Pro packaging remain Later product-depth work. |
| Durable / diff-based undo-redo history | Later | M | Med | §2.8 / §3 |
| Global synced blocks (edit-once-update-everywhere) (Pro) | Later | L | High | §3 / §5 |
| Layout revision history + restore | Later | L | High | §3 |
| Accessibility diagnostics in AnalyzeLayoutHealthAction | Later | M | Med | §3 |