Skip to content

Presentation Delivery

Presentation delivery is the shared runtime model for public widgets and Layout Builder blocks. It covers three related concerns:

ConcernWhat it controls
PresentationPreset, width, alignment, visibility, and responsive display choices
DeliveryServer-rendered output, lazy fragment output, loading strategy, connection rules, and runtime resources
InteractionsPublic triggers that open lazy widgets, lazy fragments, safe URLs, or public-action fallbacks

These settings are deliberately stored as Capell metadata, not widget props. Public rendering uses the metadata to decide wrappers, lazy placeholders, assets, and interaction triggers, then strips the metadata before a widget component receives its normal content data.

SurfacePresentation pathInteraction path
Content widget instancedata.__capell.presentationdata.__capell.interactions
Widget type defaultWidgetDefinitionData::$defaultPresentationSettingsWidgetDefinitionData::$defaultInteractionTriggers
Layout Builder block instancelayout block meta.presentationlayout block meta.interactions
Layout Builder block type defaulttype meta.presentationtype meta.interactions

Presentation settings resolve in this order:

  1. instance override;
  2. type default;
  3. presentation preset default;
  4. system default.

Existing content falls back to server_rendered, all devices, any connection, eager loading, inherited width, and stretch alignment.

Use Capell\Core\Actions\Presentation\ResolvePresentationSettingsAction when package code needs normalized settings for a widget or block. The resolver is the boundary that turns sparse editor state into complete public-safe settings.

Do not read these arrays directly in public views. Resolve them in Actions, payload builders, view components, or package render code, then pass only the public-safe result to Blade.

Use Capell\Core\Actions\Interactions\ResolveInteractionTriggersAction to normalize interaction state. The resolver accepts instance triggers first and type defaults second. Instance triggers replace type defaults when present.

Each trigger can contain:

KeyRequiredNotes
labelYesPublic button/link text.
iconNoAdmin/display hint, usually a Heroicon key.
styleNoprimary, secondary, or subtle.
target_typeYeswidget, fragment, url, or public_action.
behaviorFor lazy targetsmodal, slide_over, inline_reveal, or replace_region.
target_widgetFor widget targetsA one-item widget builder payload, e.g. [['type' => 'content', 'data' => [...]]].
fragment_referenceFor fragment targetsEncrypted reference. Layout Builder can default this to the current block fragment.
urlFor URL targetsMust be /, #, http://, or https://.
fallback_urlNoSafe fallback URL for failed or external targets.
analytics_keyNoPublic-safe key for analytics wiring.
aria_labelNoFalls back to label.
modal_sizeNosm, md, lg, xl, or screen.

Invalid triggers are dropped instead of rendering broken public controls.

Use a lazy widget when a trigger should open content that is best modelled as a Capell widget: video, gallery, form, calculator, comparison table, product picker, or any package-owned component with its own resources.

  1. The editor adds an interaction to a visible widget or block.
  2. The interaction target type is widget.
  3. The nested target_widget payload stores one normal widget.
  4. Public rendering turns that payload into an encrypted widget reference.
  5. The trigger points at /_capell/widgets/{reference}.
  6. On click, the frontend runtime fetches the target, mounts it using the chosen behaviour, loads nested resources, and activates nested interactions.

The initial public page does not include the target widget’s content, widget key, component name, package namespace, or raw data.

Use a lazy fragment when the target is a Layout Builder public block fragment rather than a standalone widget. This is for heavier page sections that already live in the layout graph.

  1. The block or block type has an interaction whose target type is fragment.
  2. If no explicit fragment_reference is stored, Layout Builder can generate an encrypted reference to the current block during public rendering.
  3. The trigger points at /_capell/fragments/{reference}.
  4. The fragment endpoint decodes the reference, revalidates the page/layout/block scope, renders only that public block fragment, and runs the public HTML safety inspection.
  5. Invalid, replayed, or unsafe references return the same generic failure.

This is separate from normal page HTML caching. Fragment responses have fragment-specific cache headers and must not be treated as full page responses.

Frontend widgets can declare CSS and JavaScript resources in PHP registration code. The public page render collects the widget definitions that appear in page content, adds eager resources to the normal asset manifest, and exposes lazy resources through a small JSON manifest consumed by resources/js/widget-runtime.js.

use Capell\Core\Data\Widgets\WidgetDefinitionData;
use Capell\Core\Enums\PresentationLoadingStrategy;
use Capell\Core\Support\Widgets\WidgetRegistry;
use Capell\Frontend\Support\Assets\FrontendResourceRegistry;
public function boot(FrontendResourceRegistry $resources, WidgetRegistry $widgets): void
{
$resources
->group('vendor.carousel')
->css('resources/css/widgets/carousel.css', buildPath: 'vendor/theme')
->js('resources/js/widgets/carousel.js', buildPath: 'vendor/theme', loading: PresentationLoadingStrategy::Visible);
$widgets->registerDefinition(WidgetDefinitionData::frontendBlade(
key: 'carousel',
component: 'vendor-theme::widgets.carousel',
resourceGroups: ['vendor.carousel'],
));
}

Use stable resource group keys. Public HTML receives generated resource IDs, not package names, component names, model IDs, field paths, or editor metadata.

Lazy fragments are Layout Builder public block fragments only. They are enabled by setting presentation delivery mode to lazy_fragment on the block instance or block type default.

The public placeholder contains only generic fragment markers and an encrypted fragment URL under /_capell/fragments/{reference}. The reference is opaque encrypted JSON and is revalidated against site, page, layout, language, container, block, and occurrence before rendering. Invalid or replayed references return a generic 404.

Fragment responses use fragment-specific cache headers and do not enter the normal page HTML cache. Above-the-fold content should remain server-rendered; lazy fragments are for below-the-fold, expensive, or optional sections.

Public fragment HTML is inspected with the same authoring-surface safety check as full page output. Do not emit editor URLs, signed admin URLs, model IDs, field paths, component names, package namespaces, block keys, or other authoring details.

Capell interactions let a widget or Layout Builder block expose a public trigger that opens another experience. Targets can be registered widgets, encrypted Layout Builder fragments, safe URLs, or public-action fallbacks. Widget targets are rendered through GET /_capell/widgets/{reference} using encrypted opaque JSON, so public HTML contains the trigger label and generic runtime attributes but not the widget type, component name, package namespace, model IDs, field paths, or target widget content.

Supported behaviours are modal, slide-over, inline reveal, and replace region. The frontend runtime handles fetching the lazy target, loading nested widget resources, keyboard close behaviour, and nested fragment/widget activation. Editors configure target widgets through the normal widget builder inside the interaction form.

Store widget interactions under data.__capell.interactions. Layout Builder block instances store interactions under layout block meta.interactions, and block type defaults use type meta.interactions.

Lazy widget targets use Capell\Frontend\Support\Widgets\OpaqueWidgetReference and Capell\Frontend\Actions\RenderLazyWidgetAction.

The encrypted reference contains:

[
'type' => 'video-player',
'data' => [
'title' => 'Product walkthrough',
'video_url' => 'https://example.com/video.mp4',
'__capell' => [
'presentation' => [
'loading_strategy' => 'interaction',
],
],
],
]

The lazy widget endpoint validates that the widget type is registered for WidgetTarget::FrontendBlade, renders the target through the normal widget runtime wrapper, runs public HTML safety inspection, and returns generic failure for invalid references.

Capell\Admin\Filament\Components\Forms\Interactions\InteractionSettingsSchema provides the shared editor controls. Content Builder stores widget interactions under data.__capell.interactions. Layout Builder stores block interactions under meta.interactions.

PresentationSettingsSchema remains responsible for presentation and delivery settings. Advanced presentation controls are permission-gated by presentation.manage_advanced; interaction controls stay editor-facing while target presentation settings remain nested under the advanced presentation schema where applicable.

For editors, keep the first layer simple:

  • choose a preset or basic layout settings;
  • add a clear trigger label and icon;
  • choose what opens;
  • choose how it opens.

Keep lower-level delivery controls behind the advanced permission. Normal editors should not need to understand encrypted references, route reservations, loading strategies, or runtime resource groups to add a video modal.

resources/js/widget-runtime.js owns interaction activation. It:

  • loads widget resource groups based on generated public IDs;
  • fetches lazy widget and fragment targets on interaction;
  • opens modal and slide-over shells;
  • handles inline reveal and replace-region behaviours;
  • activates nested widgets, nested fragments, and nested interactions inside fetched HTML;
  • removes failed fragment placeholders instead of exposing diagnostic detail.

Keep this runtime generic. Package-specific widget JavaScript belongs in registered resource groups.

SituationBehaviour
Existing content has no presentation metadataIt renders server-side with system defaults.
A trigger is missing required target dataThe resolver drops the trigger.
A lazy widget reference is invalidThe endpoint returns a generic failure.
A fragment reference is invalid or replayed for another page scopeThe endpoint returns a generic 404.
Rendered lazy HTML contains unsafe authoring surfaceThe endpoint blocks the response.
A lazy fetch fails in the browserThe runtime does not expose diagnostics in public HTML and may use a safe fallback URL if configured.