Structured Content Library — Improvement & Growth Plan
Package: capell-app/structured-content-library · Kind: package · Tier: free · Product group: Capell Foundation · Bundle: foundation · Status: Complete
1. Snapshot
Section titled “1. Snapshot”Structured Content Library provides one package-owned Eloquent model (StructuredContentItem, table structured_content_items — src/Models/StructuredContentItem.php) for nine fixed reusable business-content concepts driven by StructuredContentType (case study, testimonial, team member, service, FAQ, resource, partner, location, logo — src/Enums/StructuredContentType.php). Content is stored as a flat row (title, slug, summary, content) plus a single typed payload JSON cast to StructuredContentPayloadData (19 optional scalar fields — src/Data/StructuredContentPayloadData.php). Surfaces are admin (one Filament resource, src/Filament/Resources/StructuredContentItems/StructuredContentItemResource.php) and shared; there is no frontend surface (providers.frontend: []). Key Actions: CreateStructuredContentItemAction, UpdateStructuredContentItemAction, ListStructuredContentItemsAction, BuildPublicStructuredContentItemsAction, BuildStructuredContentSectionsAction, ImportStructuredContentItemsAction, and the HTML guard EnsurePortableContentHtmlAction. Deps: capell-app/admin, capell-app/core, filament/support, lorisleiva/laravel-actions, spatie/laravel-data, spatie/laravel-package-tools (composer.json). Marketplace summary now names the concrete reusable content types and theme-safe rendering promise. Screenshot media is open again: capell.json lists only the extension card because the prior product captures were illustrative mockups, not Capell runner output.
2. Improvements (existing functionality)
Section titled “2. Improvements (existing functionality)”-
Screenshot contract reopened and runner-app install blocked.
docs/screenshots.jsonstill declares the admin list, create form, and theme-rendering captures, but the previous mock PNG/SVG assets were removed fromdocs/screenshots/andcapell.json. The Capell runner dry-run validates the entries, but the prepared screenshot app route list has no Structured Content admin route and the package remains absent from the runner app Composer requirements, so real captures must start by installingcapell-app/structured-content-libraryin the runner app or adding another supported install path. After that, seed the Structured Content admin resource and a real theme fixture consuming published records before promotion. — positioning —docs/screenshots.json,capell.json— S -
Done/Shipped: Add a unique index on
(type, site_id, slug). The package migration now repairs duplicate scoped non-null slugs before creatingstructured_content_type_site_slug_unique, and import coverage proves generated slugs are used for dedup when incoming rows omitslug. — data integrity —database/migrations/2026_06_04_000001_add_unique_scope_slug_index_to_structured_content_items_table.php,tests/Integration/Actions/ImportStructuredContentItemsActionTest.php,tests/Integration/Models/StructuredContentItemTest.php— S -
Done/Shipped: Implemented the health check.
StructuredContentLibraryHealthChecknow probes the storage table,CapellCoremodel registration, protected-table registration, and admin resource contribution with translated labels/messages/remediations. Focused health coverage pins the probe set and failure messages. — manifest/behaviour alignment, diagnostics value —src/Health/StructuredContentLibraryHealthCheck.php,resources/lang/en/health.php,tests/Integration/Health/StructuredContentLibraryHealthCheckTest.php— S -
Done/Shipped: Auto-derive
published_atwhen publishing without a date. Create defaultspublished_atfor new published records, and update now defaults tonow()only when transitioning toPublishedwith no supplied date while preserving existing timestamps on published and non-publish updates. Evidence:UpdateStructuredContentItemActionTestcovers the publish transition, preserving an existing timestamp, and no timestamp change for non-publish transitions. — correctness/UX —src/Actions/CreateStructuredContentItemAction.php,src/Actions/UpdateStructuredContentItemAction.php— S -
Done/Shipped: Sanitize
summarythrough the portable-HTML guard. Create and update actions now passsummarythroughEnsurePortableContentHtmlActionwith field-specific validation errors, matching thecontentportability contract before values reach public DTOs. Evidence:CreateStructuredContentItemActionTestandUpdateStructuredContentItemActionTestpersist safe portable summary HTML and reject script tags plus inline event handlers without storing unsafe values. — public-output safety + contract consistency —src/Actions/CreateStructuredContentItemAction.php,src/Actions/UpdateStructuredContentItemAction.php,src/Actions/EnsurePortableContentHtmlAction.php— S -
Done/Shipped: Make the
payloadform fields type-aware. The Filament form now scopes payload inputs withGet $get('type')+->visible()and a testedpayloadFieldsForType()map, so each reusable content type shows only relevant payload fields. Evidence:StructuredContentItemResourceTestcovers FAQ, Testimonial, Location, and the full payload-field union. — admin UX, reduces data-entry error —src/Filament/Resources/StructuredContentItems/StructuredContentItemResource.php,tests/Unit/Filament/StructuredContentItemResourceTest.php— M -
Done/Shipped: Add an
archived()/trashed table filter and surface soft-deletes. The table now includesTrashedFilter, edit/delete/restore record actions, delete/restore/force-delete toolbar actions, and the edit page exposes restore/delete/force-delete header actions. — admin UX, data safety —src/Filament/Resources/StructuredContentItems/StructuredContentItemResource.php,src/Filament/Resources/StructuredContentItems/Pages/EditStructuredContentItem.php— S -
Done/Shipped: Add slug-uniqueness handling in write actions.
ResolveUniqueStructuredContentSlugActionnow reserves soft-deleted slugs and suffixes collisions per type/site scope. Evidence: create/update action coverage proves generated slug suffixing, same-slug allowance across site scopes, owner update preservation, and soft-deleted slug reservation. — data integrity, theme URL stability —src/Actions/ResolveUniqueStructuredContentSlugAction.php,src/Actions/CreateStructuredContentItemAction.php,src/Actions/UpdateStructuredContentItemAction.php,tests/Integration/Actions/CreateStructuredContentItemActionTest.php,tests/Integration/Actions/UpdateStructuredContentItemActionTest.php— M -
Done/Shipped: Expose
sort_orderediting in the table. The resource table is now->reorderable('sort_order')while the existing metadata form continues to expose directsort_orderandpublished_atedits. — admin UX —src/Filament/Resources/StructuredContentItems/StructuredContentItemResource.php— S
3. Missing Features (gaps)
Section titled “3. Missing Features (gaps)”Manifest capabilities[]: structured-content-library, structured-content-public-adapter, structured-content-section-adapter, structured-content-theme-adapter, structured-content-import.
-
Done/Shipped: Section/theme adapter capabilities are no longer advertised as delivered. The unwired
structured-content-section-adapterandstructured-content-theme-adaptercapability strings were removed fromcapell.json, whilecontent-section-adapterandtheme-adapterare now listed undercontributionTraceability.deferredContributions.StructuredContentLibraryProviderTestpreserves the shipped in-processBuildStructuredContentSectionsActioncontract and asserts the adapter claims remain deferred until real integrations exist. —capell.json(capabilities,contributionTraceability),tests/Unit/Providers/StructuredContentLibraryProviderTest.php -
Custom / extensible content types (differentiator).
StructuredContentTypeis a hard-coded 9-case enum. A structured-content library’s headline value vs. WordPress is user-defined content types. There is no registry (CapellCore::registerStructuredContentType(...)-style) for downstream packages/themes to add a type. This is the single biggest growth lever. —src/Enums/StructuredContentType.php -
Repeatable / nested field groups (table-stakes for structured content).
payloadis a single flat DTO of scalars. There is no support for repeatable groups (e.g. a service with N feature bullets, a team member with N social links, an FAQ set). Today this forces one row per item with no grouping primitive. —src/Data/StructuredContentPayloadData.php -
Relations / references between items (differentiator). No way to reference one item from another (e.g. testimonial → team member, case study → service). No
referencescolumn, morph, or relation beyondsite. —src/Models/StructuredContentItem.php -
Media / image handling (table-stakes). Payload carries only
image_alt/logo_altstrings — no image URL, asset id, or media-library integration. “Logo” and “team member” types are effectively text-only. —src/Data/StructuredContentPayloadData.php -
Per-field validation beyond HTML portability (table-stakes). Only
title(required) andcontent/summary(portability) are validated in actions.url/email/phone/country_codepayload fields have Filament-level->url()/->email()hints but no action-level validation, so imports and programmatic writes accept anything. —src/Actions/CreateStructuredContentItemAction.php,src/Data/StructuredContentPayloadData.php -
Read API exposure (gap given the “portable” pitch).
composer.jsonrequiresspatie/laravel-dataand the public DTO exists, but there is no JSON/REST endpoint or API resource (grepfinds noJsonResource/ApiResource). “Portable” currently means “in-process Action only”. A signed/public read endpoint would make the package genuinely portable to JS themes and headless consumers. — package-wide -
Versioning / revision history (differentiator). No revisions, no draft-vs-published divergence, no audit trail. Updates overwrite in place. Sibling packages (e.g. KnowledgeBase article versions) model this. —
src/Models/StructuredContentItem.php -
Export counterpart to import.
ImportStructuredContentItemsActionexists with noExportStructuredContentItemsAction, undercutting the “portable/migration tool” positioning and round-trip demo-kit story. —src/Actions/ -
Localised content (i18n gap). Manifest declares
cacheSafety.variesBy: ["site","locale"], but the model has no per-locale content columns or translation strategy — only asite_id. Cached output varying by locale would serve identical text across locales. —src/Models/StructuredContentItem.php,capell.json
4. Issues / Risks
Section titled “4. Issues / Risks”-
Done/Shipped: public payload values are sanitized at the package boundary.
BuildPublicStructuredContentPayloadActionnow decodes HTML entities before stripping dangerous blocks/tags, keeps public payload output as plain text, and drops unsafe payload URLs such asjavascript:. Evidence:BuildPublicStructuredContentItemsActionTestproves raw and entity-encoded HTML/JS is removed before public serialization or deliberately raw Blade rendering. —src/Actions/BuildPublicStructuredContentPayloadAction.php,tests/Integration/Actions/BuildPublicStructuredContentItemsActionTest.php -
Done/Shipped: Critical health check is real. Diagnostics now fail on missing storage table, missing Core model registration, missing protected-table registration, or missing admin resource contribution. —
src/Health/StructuredContentLibraryHealthCheck.php,tests/Integration/Health/StructuredContentLibraryHealthCheckTest.php -
Done/Shipped: unique constraint and null-slug import dedup are covered. Existing duplicate scoped non-null slugs are repaired before the unique index is added, and imports without an explicit
slugdeduplicate against the generated slug. Note: direct future inserts withsite_id = NULLmay still depend on database-specific nullable unique-index semantics. —database/migrations/2026_06_04_000001_add_unique_scope_slug_index_to_structured_content_items_table.php,tests/Integration/Actions/ImportStructuredContentItemsActionTest.php -
Done/Shipped: Cache safety is wired for public builders.
StructuredContentCachenow caches batched public structured-content DTOs by type set, site, locale, and version; model save/delete/restore events advance the version and flush tagged stores; and the provider registersStructuredContentItemas a frontend cache invalidation dependency whenCacheInvalidationRegistryis installed. Evidence:BuildStructuredContentSectionsActionTestproves cached second renders do not hitstructured_content_itemsand that new records invalidate cached output;StructuredContentLibraryProviderTestcovers frontend registry wiring. —src/Support/StructuredContentCache.php,src/Actions/BuildPublicStructuredContentItemsForTypesAction.php,src/Actions/BuildPublicStructuredContentItemsAction.php,src/Providers/StructuredContentLibraryServiceProvider.php,tests/Integration/Actions/BuildStructuredContentSectionsActionTest.php,tests/Unit/Providers/StructuredContentLibraryProviderTest.php -
Done/Shipped: Section rendering is batched against the query budget.
BuildStructuredContentSectionsActionresolves all requested types first, fetches public items through the batched builder once, applies per-section limits locally, and reuses cached output on subsequent renders. Evidence: package coverage asserts a three-section render uses no more than onestructured_content_itemsselect and cached rerenders use zero. —src/Actions/BuildStructuredContentSectionsAction.php,src/Actions/BuildPublicStructuredContentItemsForTypesAction.php,tests/Integration/Actions/BuildStructuredContentSectionsActionTest.php -
Test gaps. 49 package tests pass in the current structured-content-library slice. Covered: data mapping, enum labels, provider/manifest declarations, resource page wiring, payload field type mapping, Filament create/edit save-path Action delegation, CRUD actions, slug uniquing, portable-HTML rejection (create + update), summary portable-HTML validation, list ordering/site filtering, build-public + limit, public payload sanitization, batched/cached build-sections, cache invalidation, provider cache registry wiring, import create/update/skip, model casts/table install. Not covered:
archived()/draft()scopes; soft-delete visibility behaviour;published_at-in-future exclusion edge; import with null slug. No arch test asserting public-DTO field whitelist. —tests/ -
payloadform fields usedehydrateddefaults implicitly. All payload sub-fields are always-presentTextInput/Textarea; an empty payload still serialises 19 null keys intoStructuredContentPayloadData. Harmless but bloats stored JSON and public output. —src/Filament/Resources/.../StructuredContentItemResource.php -
i18n: only
entranslations.resources/lang/en/{admin,status,type,validation}.phponly. All labels are translation-keyed (good), but no other locale ships, and content itself is single-locale (see §3). —resources/lang/
5. Marketplace & Positioning
Section titled “5. Marketplace & Positioning”This is a free / foundation / bundled package (product.tier: free, bundle: foundation, commercial.proposedLicense: free, requestedCertification: first-party). Its strategic role is to be the typed-content substrate other paid packages and themes consume — so its value is measured by adoption depth, not standalone revenue.
-
marketplace.summarycritique. Shipped: “A typed content library for testimonials, case studies, team members, FAQs, services and more — reusable, theme-safe records your themes render anywhere.” Naming the concrete types is the hook; “theme-safe” signals the portability guard that differentiates it from a free-text block. -
composer.jsondescription critique. Shipped: “Typed, portable content records (testimonials, case studies, team, FAQs, services…) that Capell themes and packages render safely.” The manifest description keeps the longer nine-type list while Composer carries the shorter marketing line. -
free/bundle vs premium. Correctly free/foundation — it should be the gravity well that makes premium packages (content-sections, themes, automation) more valuable. Keep the model/Actions free; the premium upsell surface is layered features: revisions, API exposure, custom-type registry, media handling (§3). Do not paywall the core read Actions — that would break the foundation role.
-
Screenshot / media status.
capell.jsoncurrently lists only the extension card. Required captures remain blocked until the screenshot runner app installs the package: (1) the real Filament list table with type/status badges, (2) the create form showing type + payload fields, and (3) a route-backed theme fixture rendering published items to prove the “theme renders the data” story. -
Platform-pitch contribution. Structured/typed content is a top differentiator vs. WordPress (which leans on free-text blocks + plugins for custom types). This package is the credibility anchor for that pitch — but only once custom content types (§3) and a wired section/theme adapter (§3) exist. Today the pitch is partly aspirational: the adapters are manifest strings, and the type set is fixed.
-
Keywords / tags (8–12):
structured-content,typed-content,content-modeling,reusable-content,testimonials,case-studies,faqs,team-members,headless-content,cms-foundation,theme-content,portable-content.
6. Prioritized Roadmap
Section titled “6. Prioritized Roadmap”| Item | Bucket | Effort | Impact | Section ref |
|---|---|---|---|---|
Done/Shipped: Sanitize summary via portable-HTML guard. Evidence: CreateStructuredContentItemActionTest and UpdateStructuredContentItemActionTest persist safe portable summary HTML and reject script tags plus inline event handlers. | Done | S | High (public safety) | §2 |
| Done/Shipped: Implement real health check (drop stub). Evidence: storage table, Core model, protected table, and admin resource diagnostics are translated and covered. | Done | S | High (false-green critical) | §2, §4 |
Done/Shipped: Add unique index on (type, site_id, slug) + fix null-slug import dedup. Evidence: migration deduplicates legacy scoped slugs before adding the unique index, and import tests prove omitted slugs still deduplicate through the generated slug. | Done | S | High (data integrity) | §2, §4 |
Done/Shipped: Wire section/theme adapter OR mark capabilities deferred. Evidence: capell.json removes unwired adapter capabilities, marks adapter contributions deferred, and StructuredContentLibraryProviderTest asserts the truthful manifest contract. | Done | M | High (manifest honesty) | §3 |
| Done/Shipped: Document/assert payload public-output escaping contract (+ XSS test). Evidence: public payload text decodes entities before stripping dangerous markup, unsafe URLs are dropped, and tests cover raw/encoded HTML/JS. | Done | S | High (public safety) | §4 |
Done/Shipped: Default published_at to now() on publish transition. Evidence: UpdateStructuredContentItemActionTest covers the publish transition, preserving an existing timestamp, and no timestamp change for non-publish transitions. | Done | S | Med (correctness) | §2 |
| Done/Shipped: Add Filament test for admin save path (Action delegation). Evidence: fixture page tests call the create/edit protected save paths and assert persisted Action behavior. | Done | S | Med (test gap) | §4 |
Done/Shipped: Type-aware payload form fields (->visible() by type). Evidence: payload map coverage scopes all 19 payload fields across the nine content types. | Done | M | Med (admin UX) | §2 |
Done/Shipped: Trashed filter + restore/delete actions + reorderable table. Evidence: resource table exposes TrashedFilter, delete/restore bulk and record actions, and ->reorderable('sort_order'); edit page exposes restore/delete/force-delete actions. | Done | S | Med (admin UX/safety) | §2 |
| Done/Shipped: Slug collision uniquing in write actions. Evidence: create/update coverage proves scoped suffixing, owner preservation, cross-site allowance, and soft-deleted slug reservation. | Done | M | Med (URL stability) | §2 |
| Done/Shipped: Implement declared caching + invalidation registry; benchmark budget. Evidence: batched public builder, versioned tagged cache, model-event invalidation, frontend registry wiring, and query-count tests. | Done | M | Med (perf, manifest match) | §4 |
| Custom/extensible content-type registry | Later | L | High (differentiator) | §3, §5 |
| Repeatable/nested payload field groups | Later | L | High (differentiator) | §3 |
| Read API (public JSON resource/endpoint) | Later | M | Med (portability story) | §3, §5 |
| Revisions/versioning + Export action; media handling; i18n content | Later | L | Med (premium upsell) | §3, §5 |
| Generate real Capell runner screenshots for admin list, create form, and theme rendering. Blocked until the runner app installs/seeds this package and exposes the Structured Content admin resource/theme fixture. | Later | S | Med (positioning) | §5 |
| Shipped: rewrite marketplace summary/description | Done | S | Med (positioning) | §5 |