Content Sections — Improvement & Growth Plan
Package: capell-app/content-sections · Kind: package · Tier: free · Product group: Capell Foundation · Bundle: foundation · Status: Draft
1. Snapshot
Section titled “1. Snapshot”Content Sections ships a single Eloquent model (Section, nested-set, soft-deletes, translatable, media, userstamps) and a catalog of reusable, blueprint-backed page-section widgets that editors manage in a Filament SectionResource and that render on the public frontend via package-owned Blade components. It is a runtime + admin package (surfaces: ["admin","frontend"]) that bridges into capell-app/block-library (each registered section is exposed as a section.{key} block) and capell-app/layout-builder (public render is driven by SectionPublicWidgetPayloadContributor, which the layout graph calls per placed widget). Hard deps: admin, block-library, core, frontend, layout-builder; optional integration with publishing-studio (workspace clone/finalize) and public-actions.
Section types actually shipped. DefaultSectionDefinitionProvider registers 17 definitions: content, hero, testimonial, accordion, call_to_action, comparison, counter, divider, faq, features, logos, pricing, stats, table, tabs, team, timeline (src/Support/DefaultSectionDefinitionProvider.php). Each maps to a configurator in SectionConfiguratorEnum and a Blade view under resources/views/components/section/widgets/. RichSectionConfigurator (507 lines, src/Filament/Configurators/Sections/RichSectionConfigurator.php) is the shared base class that 14 richer configurators extend. Two frontend components (section.widget, section.team-member) are registered with the frontend component registry; the rest render through the dynamic-component fallback.
Marketplace summary: The manifest now leads with the 17 ready-to-use, themeable page sections and Foundation bundle positioning. Screenshots: the marketplace block now declares the extension card plus four Capell runner PNGs for the admin index, create form, edit form/assets panel, and dark-mode index. docs/screenshots.json defines a richer 5-entry capture contract; the selector-modal and frontend widget gallery entries now point at dedicated route-backed fixtures so runner captures do not fall back to Dashboard or the generic demo page.
2. Improvements (existing functionality)
Section titled “2. Improvements (existing functionality)”- Escape public section output (single render helper) — see §4.1. — Public Blade renders editor content with
{!! !!}across every widget; the cleanest fix point is a sanitisation pass on the data the contributor builds rather than 20 templates. —src/Support/SectionPublicWidgetPayloadContributor.php(sectionData/renderSection) — effort M. - Replace the stub health check with real surface/install assertions. —
ContentSectionsHealthCheckonly returns'^4.0'; it never checks anything, yet the manifest labels it “package surfaces, providers, and install health are discoverable by Diagnostics” atseverity: critical. Implement checks that theSectionmorph alias is registered, theSectionResourceis contributed, thesectionstable exists, and the default-section registry is populated. —src/Health/ContentSectionsHealthCheck.php,capell.jsonhealthChecks — effort S. - Shipped 2026-06-07: reduce duplicated per-section render work before relying on the declared budget. —
SectionPublicWidgetPayloadContributornow caches the per-widget section payload for the current contributor instance, sodata()andhtml()reuse the same rendered section fragments instead of rebuilding and rerendering every section twice whenincludeHtmlis enabled. Coverage asserts the top-level widget HTML matches the cached section payload HTML. —src/Support/SectionPublicWidgetPayloadContributor.php,tests/Feature/PublicLayoutGraphSectionPayloadTest.php - Shipped 2026-06-07: decouple the cross-package
widget_assetswrite in publish finalize. — Layout Builder now ownsRepointWidgetAssetReferencesAction, and Content Sections calls that Action fromFinalizeSectionPublishActioninstead of issuing a rawDB::table('widget_assets')->update(...)from its service provider. —packages/layout-builder/src/Actions/RepointWidgetAssetReferencesAction.php,src/Actions/FinalizeSectionPublishAction.php - Shipped 2026-06-07: move provider publishing wiring into Actions per package convention. —
ContentSectionsServiceProvidernow registers Publishing Studio callbacks that delegate toCloneSectionIntoWorkspaceActionandFinalizeSectionPublishAction; direct publishing tests cover clone and finalize behavior. —src/Actions/CloneSectionIntoWorkspaceAction.php,src/Actions/FinalizeSectionPublishAction.php,tests/Feature/Publishing/SectionWorkspacePublishTest.php - Closed 2026-06-07: stale
simple-listwidget claim reconciled. —resources/views/components/section/widgets/simple-list.blade.phpis no longer present, anddocs/overview.mdlists only the 17 registered section widget families fromDefaultSectionDefinitionProvider. No orphanedsimple-listpublic widget remains to register or document. —resources/views/components/section/widgets/,src/Support/DefaultSectionDefinitionProvider.php,docs/overview.md - Shipped 2026-06-06: Rename
PopularSectionConfiguratorto its actual role. — The shared base configurator is nowRichSectionConfigurator, and the 14 richer section configurators extend it. The class name no longer implies a “popular sections” section type. —src/Filament/Configurators/Sections/RichSectionConfigurator.php+ subclasses — effort S. - Localise demo content instead of hardcoded English. —
BuildSectionDemoDataActionreturns English labels/descriptions/marketing copy directly ('Reusable rich text content.', plan names, FAQ copy) and short-circuits the translation keys viafallbackLabel/fallbackDescription. Demo seeds shown in a marketplace gallery should respect locale. —src/Actions/BuildSectionDemoDataAction.php— effort M.
3. Missing Features (gaps)
Section titled “3. Missing Features (gaps)”Manifest capabilities: [] is empty, so there is nothing to tie improvements to — that itself is gap #1: a section-catalog package must advertise capabilities (see §5). Against section-catalog norms:
- Layout / spacing / background controls are inconsistent.
heroreadsmeta['alignment']andwidget.blade.phphas acolorswitch, but there is no shared, per-section background / padding / max-width / alignment contract. Editors get different controls per widget instead of a common “section appearance” block. Table-stakes for a section library. - No responsive / breakpoint controls in this package.
docs/overview.mddescribes breakpoint editing and layout areas, but that behaviour lives in layout-builder, not here; the section widgets themselves expose no responsive options. Differentiator opportunity: per-section responsive visibility/column overrides owned by the section. - Accessibility is unaudited.
faq/accordionuse<details>/<summary>(good), but raw-HTML injection (§4) plus no heading-level config (herohard-codes<h1>, others<h2>) risks duplicate-H1 and broken document outline when multiple sections render on one page. No ARIA ontabs/counter. Thesection-widget-galleryscreenshot entry explicitly asks for anonymous-safe output — accessibility should ride along. - No anchor / ID / “jump link” support on sections, despite FAQ/timeline/pricing being natural deep-link targets.
- No content-block reuse surfaced to editors as variants/presets. Sections are reusable records, but there is no notion of a saved style preset or duplicate-as-template beyond raw
replicate. - Differentiator vs table-stakes: the 17-type catalog + block-library bridge is solid table-stakes. The differentiators would be (a) a shared section-appearance/responsive contract, (b) accessible-by-default rendering with configurable heading levels and anchors, (c) safe rich-text handling — none of which exist yet.
4. Issues / Risks
Section titled “4. Issues / Risks”4.1 Stored-XSS in public output (CRITICAL — public-output safety)
Section titled “4.1 Stored-XSS in public output (CRITICAL — public-output safety)”Editor-controlled content is rendered as raw HTML on the anonymous frontend with no sanitisation:
{!! $summary !!}appears incontent,hero,faq,call-to-action,pricing,logos,counter,stats,team,tabs,accordion,timeline, andfeaturesviews (resources/views/components/section/widgets/*.blade.php), with public safety now enforced at the payload contributor boundary.- Nested meta is also raw in package views after sanitised payload assembly:
{!! $question['answer'] !!}(faq.blade.php:35),{!! $item['content'] !!}(accordion.blade.php:44), and{!! $tab['content'] !!}(tabs.blade.php:48). - The source data is unsanitised:
SectionPublicWidgetPayloadContributor::renderSectionwraps summary innew HtmlString((string) $data['summary'])(line 139), andmetaflows straight fromSection->metaJSON viametaFor()with onlyarray_replace_recursive. There is no HTML Purifier / allow-list anywhere in the package. - Risk: any actor who can edit a section’s translation
content/summaryormeta(a non-super editor withupdatepermission, or any upstream package that writes section meta) can persist<script>/event-handler HTML that executes for every anonymous visitor. This directly violates the public-output-safety rule. Fix at the contributor boundary (§2.1) by passing section content through a shared sanitiser before it reachesHtmlString/the views.
4.2 Unvalidated icon strings into svg() (render safety)
Section titled “4.2 Unvalidated icon strings into svg() (render safety)”features.blade.php:29 and counter.blade.php:30 do {!! svg($feature['icon'], ...)->toHtml() !!} where $feature['icon'] / $counter['icon'] is an editor-supplied meta string. Confirm svg() cannot resolve arbitrary filesystem paths or emit attacker-controlled markup; at minimum validate against a known icon allow-list.
4.3 Test gaps (no proof of public safety)
Section titled “4.3 Test gaps (no proof of public safety)”- Shipped 2026-06-07: public-output safety coverage now includes authenticated non-admin visitors. Sanitisation tests assert anonymous payload/html safety and a normal signed-in frontend visitor receives the same safe public graph without scripts, authoring markers, signed editor URLs, or package internals. —
tests/Feature/SectionPublicOutputSanitisationTest.php - The health check is only asserted to exist (
tests/Unit/ContentSectionsCoverageTest.php), with no behavioural coverage — unsurprising, since it has no behaviour. - Direct publishing tests now cover the workspace clone Action and the finalized widget-asset repoint through the Layout Builder boundary Action. The remaining package-local coverage gap is a full completion review across the screenshot fixture states.
4.4 Cache safety vs declared budget
Section titled “4.4 Cache safety vs declared budget”Closed 2026-06-06: capell.json now declares content-sections cache tags and Section invalidation sources for created, updated, deleted, restored, and force-deleted events. Blueprint and linked-page URL dependencies remain broader cache-depth follow-up, but the stale queueInvalidation/empty-source mismatch is closed for the package-owned section model.
4.5 Performance budget realism
Section titled “4.5 Performance budget realism”frontendRenderBudgetMs: 20 is still tight, but the worst duplicate render path is closed: the contributor reuses cached per-widget section payloads across data() and html() calls. Multiple sections in one widget container can still multiply view-render work, so future performance work should focus on seeded end-to-end timing inside a full Capell app rather than adding noisy package-level wall-clock assertions. The public Blade itself correctly avoids DB queries (relations are pre-hydrated and guarded with relationLoaded), which is good.
4.6 i18n
Section titled “4.6 i18n”Translation files exist (resources/lang/en/*), but BuildSectionDemoDataAction bypasses them with hardcoded English (§2.8), and several blades emit literal English only via __() keys that may be unmapped. Verify every capell-content-sections::section.{key}.label/description key is defined, since DefaultSectionDefinitionProvider references all 17.
4.7 Manifest / docs accuracy (tech debt)
Section titled “4.7 Manifest / docs accuracy (tech debt)”contributes: []while the provider contributes an admin resource, ~18 configurators, asectionpage type, an asset, two frontend components, and the public widget contributor;contributionTraceability.deferredContributionslistsadmin-resource, asset, configurator, model, page-type, route, schema-extender— none declared incontributes[].permissions: []whileSectionPolicyrelies on Shield permissions (view_any,view,create,update,delete,restore,force_delete,replicate,reorder).surfacesmismatch:capell.json=["admin","frontend"];README.md= “Filament admin, Livewire, database” (drops frontend, adds Livewire/database).database.requiredTables: []though the package owns and requires thesectionstable.- Closed 2026-06-07: stale Layout Builder mutation docs were removed.
docs/mutations.mdhas been deleted becausesrc/Actions/Mutations,ContentSectionsStateData,NormalizeContentSectionsStateAction, andLayoutMutationResultDataare not Content Sections APIs.docs/overview.mdnow describes the actual reusable-section editing workflow and explicitly leaves layout modes, breakpoints, and undo/redo tocapell-app/layout-builder.
5. Marketplace & Positioning
Section titled “5. Marketplace & Positioning”Role. Free, foundation-bundled, first-party — correct. This package is platform glue: it is what makes a Capell site able to assemble pages from reusable, themeable content blocks, and it is the producer side of the block-library/layout-builder pairing. That makes it a keystone of the foundation pitch (“compose pages from reusable sections, no markup in content fields”), so its marketing should sell the catalog and reuse, not describe itself tautologically.
Current summary / description critique. The marketplace summary now names the 17-section catalog, reuse workflow, safe Blade rendering, and Foundation bundle fit. Composer copy is still shorter than the ideal positioning below and can be tightened when the screenshot fixture work lands.
Improved summary (marketplace): “Seventeen ready-to-use, themeable page sections — hero, FAQ, pricing, stats, testimonials, team, comparison, timeline and more — that editors reuse across pages and developers render through safe, package-owned Blade. Bundled free with Capell Foundation.”
Improved composer description: “A free catalog of 17 reusable, blueprint-backed page sections (hero, FAQ, pricing, stats, team, comparison, timeline, and more) for Capell, rendered through layout-builder and block-library.”
Free/bundle vs premium upsell. Keep the catalog free — it is foundation table-stakes and drives adoption of the paid layout/publishing tooling. The natural premium upsell line is advanced section types and capabilities (A/B-testable sections, personalised/segmented sections, form/CRM sections, animation presets), which the demo copy already hints at (“Advanced widgets stay in optional packages”, “Pro bundle: 0”). Position this package as the on-ramp and reserve those as paid add-on packages.
Screenshot / media gaps. The package now promotes real Capell runner admin captures for the section index, create form, edit form/assets panel, and dark-mode index. Dedicated fixture routes now exist for the selector modal and anonymous frontend widget gallery; the remaining media work is to recapture and promote those route-backed PNGs once the screenshot runner is producing styled Capell output. A per-section thumbnail strip would be the single highest-leverage media improvement.
Suggested keywords/tags (8–12): page-sections, content-blocks, reusable-content, hero, pricing-section, faq, testimonials, cms-sections, block-library, layout-builder, filament, foundation.
6. Prioritized Roadmap
Section titled “6. Prioritized Roadmap”| Item | Bucket | Effort | Impact | Section ref |
|---|---|---|---|---|
| Shipped 2026-06-04: sanitise editor HTML at the contributor boundary (kill stored-XSS) | Done | M | Critical | §2.1 / §4.1 |
| Shipped 2026-06-04: add anonymous public-output sanitisation tests | Done | M | Critical | §4.3 |
Shipped 2026-06-04: implement real ContentSectionsHealthCheck | Done | S | High | §2.2 / §4.3 |
Shipped 2026-06-04: validate svg() icon meta against an allow-list | Done | S | High | §4.2 |
Shipped 2026-06-04: fix manifest accuracy: contributes, permissions, tables, surfaces | Done | S | Med | §4.7 |
| Shipped 2026-06-07: Benchmark/reduce per-section render cost against the declared budget | Done | S | High | §2.3 / §4.5 |
Declare real cacheSafety.invalidationSources / register cache dependency | Done | S | Med | §4.4 |
| Shipped 2026-06-07: Add explicit non-admin public-safety assertions for authenticated frontend visitors | Done | S | High | §4.3 |
Closed 2026-06-07: Remove or wire orphaned simple-list widget (and the overview doc line) | Done | S | Med | §2.6 |
| Shipped 2026-06-07: Extract workspace clone + publish-finalize into Actions behind a layout-builder contract | Done | M | Med | §2.4 / §2.5 |
| Done/Shipped: Add selector-modal and frontend widget-gallery fixture routes; promote remaining shots | Done | M | High | §5 — dedicated /screenshot-fixtures/content-sections/* routes now replace discarded Dashboard/generic fallbacks; promotion waits for styled runner recapture. |
Shipped 2026-06-07: Fix/relocate stale docs/mutations.md + overview editor-workflow bleed | Done | S | Med | §4.7 |
| Add shared section-appearance contract (background/spacing/alignment/heading level) | Later | L | High | §3 |
| Add per-section anchor IDs + accessible defaults (ARIA, configurable headings) | Later | M | Med | §3 |
Localise BuildSectionDemoDataAction demo content | Later | M | Low | §2.8 / §4.6 |
Shipped 2026-06-06: Rename PopularSectionConfigurator → RichSectionConfigurator | Done | S | Low | §2.7 |