Filament Peek — Improvement & Growth Plan
Package: capell-app/filament-peek · Kind: package · Tier: free · Product group: Capell Foundation · Bundle: foundation · Status: Complete
1. Snapshot
Section titled “1. Snapshot”capell-app/filament-peek is a thin first-party integration that wires the upstream pboivin/filament-peek Filament plugin (composer.json require) into Capell Admin, then layers Capell-specific value on top: it adds a Changes preview action to the Page edit header (src/Filament/Actions/PeekPagePreviewAction.php), captures the editor’s unsaved Page form state plus live Layout Builder state into a short-lived cache snapshot (src/Actions/CreatePagePreviewSnapshotAction.php, StoreLayoutBuilderPreviewStateAction.php), and renders that snapshot through the real Capell frontend pipeline behind a temporary signed route (src/Actions/RenderPagePreviewSnapshotAction.php, src/Http/Controllers/PagePreviewController.php). The upstream plugin only supplies the iframe modal shell and open-preview-modal Livewire event; all of the snapshot, render, auth, and Layout-Builder-overlay logic is genuinely first-party and is the bulk of the 1,307 LoC under src/. It owns no DB tables/settings (capell.json database.migrations: false), surfaces on admin + frontend, and declares one capability (page-preview). Marketplace summary now leads with unsaved Page and Layout Builder previews through private, expiring, signed live-theme links. Marketplace screenshots in capell.json now include the extension card and real Capell runner captures for the Page edit preview action group; the older static desktop/mobile hero artwork is no longer promoted as product media. The signed modal and expired-preview product states remain optional runner follow-ups because the current runner cannot perform sequential grouped-action clicks or mint temporary signed route URLs during capture.
2. Improvements (existing functionality)
Section titled “2. Improvements (existing functionality)”- Done / Shipped: remove the dead
pathfield from the snapshot. — Evidence:PagePreviewSnapshotDatanow contains only token/user/page/form/workspace/Layout Builder state,CreatePagePreviewSnapshotActionno longer writespath, andrg "path:" packages/filament-peek/src packages/filament-peek/testsfinds no snapshot-path assignment. —src/Data/PagePreviewSnapshotData.php,src/Actions/CreatePagePreviewSnapshotAction.php— S - Done / Shipped: move snapshot read into a dedicated action. — Evidence:
FindPagePreviewSnapshotActionowns cache-key lookup and DTO hydration, whilePagePreviewController,PeekPagePreviewActionTest, andSnapshotActionTestcallFindPagePreviewSnapshotAction::run($token)instead of reading through the create action. —src/Actions/FindPagePreviewSnapshotAction.php,src/Http/Controllers/PagePreviewController.php,tests/Unit/SnapshotActionTest.php— M - Done / Shipped: de-duplicate preview context helpers. — Evidence:
CreatePagePreviewSnapshotAction,FindPagePreviewSnapshotAction, andStoreLayoutBuilderPreviewStateActionall useConcerns\ResolvesPreviewContextfor Filament/Auth user fallback, preview cache store resolution, TTL clamping, snapshot cache keys, and Layout Builder preview cache keys. —src/Concerns/ResolvesPreviewContext.php,src/Actions/CreatePagePreviewSnapshotAction.php,src/Actions/StoreLayoutBuilderPreviewStateAction.php— S - Done / Shipped: device/responsive preview controls. — Capell now bridges
capell-filament-peek.preview.device_presetsinto the upstreamfilament-peek.devicePresetsmodal config after package config is merged, with fullscreen/tablet/mobile defaults and provider/action coverage. —config/capell-filament-peek.php,src/Providers/FilamentPeekServiceProvider.php,tests/Unit/FilamentPeekProviderTest.php,tests/Feature/PeekPagePreviewActionTest.php— M - Done / Shipped: visible admin-only preview ribbon. — Preview responses now inject a private “Unsaved preview - not published” ribbon into rendered HTML while the route keeps
private, no-storeandX-Robots-Tagheaders. The ribbon is rendered only by the signed preview action path and is covered by route tests. —src/Actions/RenderPagePreviewSnapshotAction.php,resources/views/preview-ribbon.blade.php,resources/lang/en/actions.php,tests/Feature/PagePreviewRouteTest.php— M - Cap snapshot payload size. —
formStateandlayoutBuilderState.containersare serialized verbatim into the cache with no size guard; a large Layout Builder page can push a multi-MB blob per click per user. Add a soft size check / log-and-truncate beforeCache::put. —src/Actions/CreatePagePreviewSnapshotAction.php,src/Actions/StoreLayoutBuilderPreviewStateAction.php— S - Reconcile
capell.jsonadminQueryBudget: 40with the real render cost. —RenderPagePreviewSnapshotAction::renderSnapshot()eager-loads ~12 relations and may issue additionalfind()queries inresolveSite/resolveLayout/previewTranslations(per-languageLanguage::find) andRegisterLayoutBuilderPreviewWidgetsAction(widgets + assets + target models). The 40-query admin budget likely doesn’t reflect the frontend render path (whosefrontendRenderBudgetMsis0/unset). Add a budget that covers the preview render and a regression test asserting query count. —capell.json,src/Actions/RenderPagePreviewSnapshotAction.php— M
3. Missing Features (gaps)
Section titled “3. Missing Features (gaps)”Capability declared: capabilities: ["page-preview"]. Against common CMS preview norms:
- Done / Shipped: responsive / device-frame preview — Capell exposes upstream Peek device presets through package config with fullscreen, tablet, and mobile defaults. Remaining depth is product screenshot recapture of the modal states.
- Share-a-preview link for non-editors (reviewer/stakeholder) — currently a preview is locked to the creating admin (
userOwnsSnapshot,PagePreviewController.php:74-87). There is no way to send a client/editor-without-edit-rights a read-only preview. This is the single biggest differentiator gap, and it overlaps directly with publishing-studio’sWorkspacePeekPreviewAction+GenerateWorkspacePreviewUrlAction(packages/publishing-studio/.../Actions/WorkspacePeekPreviewAction.php), which already mints workspace preview URLs via the same upstreamPeekmodal andopen-preview-modalevent. Decide ownership: either filament-peek owns a generic “shareable preview token” primitive that publishing-studio consumes, or this stays workspace-only. Today they are two parallel, independently-built preview actions sharing only the upstream facade. - Preview of draft / unpublished pages as such — the render path overlays unsaved form state but always authorizes via
Gate::authorize('update', $page)and renders through the live pipeline; there’s no explicit “preview this scheduled/visible_from-future page” mode.visible_from/visible_untilare copied intopageAttributes()but their effect on the rendered output is untested. Gap (partial). - Live/auto-refresh preview — snapshot is a point-in-time blob; editing in the form does not live-update the open preview iframe. Upstream has a builder-editor live-preview mode that isn’t wired. Differentiator gap.
- Builder-editor (inline) preview —
pboivin/filament-peekships a “Builder Editor” side-by-side preview; Capell only uses the modal iframe. The Layout Builder integration captures state for the modal render but doesn’t offer the upstream split-screen builder preview. Gap. - Preview for non-Page pageables —
RenderPagePreviewSnapshotActionis hard-bound toCapell\Core\Models\Page;StoreLayoutBuilderPreviewStateActionalready accepts the broaderPageablecontract, so the create/render side is the only thing blocking previewing other pageable models. Gap.
4. Issues / Risks
Section titled “4. Issues / Risks”- Upstream maintenance/version risk. — Hard dependency on
pboivin/filament-peek: ^4.0(composer.json). The package is pinned to that author’s Filament-version cadence; a Filament major that the upstream lags on will block Capell. The Capell surface area touching upstream is tiny (Peek::registerPreviewModal(),FilamentPeekPlugin::make(), theopen-preview-modalevent — seePeekPagePreviewAction.php:49,FilamentPeekPanelExtender.php:15), so a fallback/vendored modal is feasible if upstream stalls. Document the blast radius. —composer.json,src/Filament/Extenders/FilamentPeekPanelExtender.php - Done / Shipped: real health-check probes replaced the stub. — Evidence:
FilamentPeekHealthCheck::runDiagnostics()now checks the signed preview route, snapshot/render action resolution, upstreampboivin/filament-peekavailability, and configured preview cache-store reachability;FilamentPeekHealthCheckTestcovers the healthy path, an invalid cache-store failure, and diagnostic output that avoids leaking snapshot/cache-key data. —src/Health/FilamentPeekHealthCheck.php,tests/Unit/FilamentPeekHealthCheckTest.php,capell.json - Done / Shipped: Layout Builder preview-state contract. — The cross-package state store now implements
StoresLayoutBuilderPreviewState, and Layout Builder verifies the resolved optional action implements that contract before invokinghandle()orclear(). This locks thehandle/clearsignatures and keeps the single-argument clear path covered. —src/Contracts/StoresLayoutBuilderPreviewState.php,src/Actions/StoreLayoutBuilderPreviewStateAction.php,../layout-builder/src/Livewire/Filament/Concerns/ManagesLayoutBuilderState.php,tests/Unit/SnapshotActionTest.php - Preview auth depends on
updateability everywhere — correct, but double-edged. — BothPeekPagePreviewAction(->authorize(... Gate::allows('update', $record))) andRenderPagePreviewSnapshotAction::renderSnapshot()(Gate::authorize('update', $page)) gate onupdate. That’s the right default and is covered by the “another user” + “unsigned” tests, but it means a user who can edit a page but has narrower view scopes still renders the full public page through the preview. Confirm no field/section-level authorization is bypassed by the render path. —src/Http/Controllers/PagePreviewController.php,src/Actions/RenderPagePreviewSnapshotAction.php - Done / Shipped: public-output-safety render rejection is covered. — Evidence:
PagePreviewRouteTestnow rejects validly-signed preview URLs for unauthenticated requests and for authenticated non-admin snapshot owners before the preview renderer output appears;PagePreviewControllertreats render-path authorization failures as private 403 responses instead of generic render errors. —tests/Feature/PagePreviewRouteTest.php,src/Http/Controllers/PagePreviewController.php - Done / Shipped:
Cache-Controlliteral is aligned. — Evidence:PagePreviewController::makePrivate()and the preview route tests now use the same literal,no-store, private, for private preview and friendly error responses. —src/Http/Controllers/PagePreviewController.php,tests/Feature/PagePreviewRouteTest.php - i18n is solid for messages, thin elsewhere. — User-facing strings are translated (
resources/lang/en/actions.php,errors.php) and the error Blade only echoes translated$title/$body(no leaked internals — good). But the error view hard-codes<html lang="en">(resources/views/preview-error.blade.php:2) regardless of the editor’s locale, and onlyentranslations ship. Localize thelangattribute and add at least one second locale to prove the translation keys are real. —resources/views/preview-error.blade.php,resources/lang/ - Done / Shipped:
CHANGELOG.mdand proprietaryLICENSEare present. — Evidence:CHANGELOG.mdtracks the Filament Peek cleanup work underUnreleased, andLICENSEnow matches the Composer manifest’s proprietary license declaration. — package root - Test gaps (enumerated). Covered: snapshot create/find/TTL/ownership (
SnapshotActionTest,PeekPagePreviewActionTest), unsigned/expired/cross-user/unauthenticated/non-admin route rejection, unsaved name/title/body + featured-image/social-image overlay render, expired and render-failure friendly responses, provider extender wiring + “not installed” short-circuit (FilamentPeekProviderTest), upstream device-preset config, health-check probes (FilamentPeekHealthCheckTest), Layout Builder widget+asset registration (RegisterLayoutBuilderPreviewWidgetsActionTest), and the typed preview-state contract. Remaining useful depth:withWorkspaceContext()path, multi-languagecurrentTranslationselection, and query-count/performance budget. —tests/
5. Marketplace & Positioning
Section titled “5. Marketplace & Positioning”This is a foundation/bundled package (product.tier: free, bundle: foundation, commercial.proposedLicense: free, requestedCertification: first-party). Functionally it’s a thin wrapper shell (upstream modal) around a substantial first-party engine (snapshot + frontend-overlay render). It earns a marketplace listing on the strength of the Capell-specific unsaved-state preview, but it should be positioned as a bundled Foundation capability, not a standalone paid add-on — its value is only realized alongside admin + frontend (hard requires) and is amplified by layout-builder/publishing-studio. If the marketplace has a “platform feature, included” lane, it belongs there rather than as an independent SKU.
- Done / Shipped:
capell.jsonsummarywas rewritten. — Evidence:capell.jsonnow leads with the editor outcome: preview unsaved page and Layout Builder edits through the live theme via a private, expiring, signed link, with nothing written until save. - Done / Shipped: Composer
descriptionwas rewritten. — Evidence:composer.jsonnow describes private, expiring previews of unsaved Page and Layout Builder changes rendered through the live frontend pipeline without persisting draft state. - Screenshot contract partially closed.
capell.jsonnow promotes real Capell runner PNG captures for the Page edit preview action group in light and dark admin themes, replacing the broken blank/loading product PNGs. The signed preview modal and expired-preview error entries are retained as optional runner follow-ups until the runner supports sequential grouped-action clicks and temporary signed route URL generation. - Platform-pitch contribution. Reinforces Capell’s “safe editing” story: edits are previewable before they’re public, and (per
docs/) preview state is provably never written into Pages/Layouts/translations/workspaces. Pairs in the pitch with publishing-studio (workspace review) and layout-builder (visual authoring) as the “see-before-you-ship” trio. - Keywords/tags (8–12):
page preview,unsaved changes preview,draft preview,signed preview link,live theme preview,layout builder preview,editorial workflow,filament admin,content safety,no-persistence preview,responsive preview,cms preview.
6. Prioritized Roadmap
Section titled “6. Prioritized Roadmap”| Item | Bucket | Effort | Impact | Section ref |
|---|---|---|---|---|
Shipped: Real health-check probes (routes/actions/Peek/cache) replaced the stub — evidence: FilamentPeekHealthCheckTest covers healthy diagnostics, invalid cache-store failure, and non-leaking messages. | Done | S | High | §4 |
Shipped: Add unauthenticated + non-admin render-rejection tests (public-safety rule) — evidence: PagePreviewRouteTest covers signed anonymous rejection and signed non-admin owner rejection before renderer output. | Done | S | High | §4 |
| Future runner follow-up: real Capell runner product screenshots for the Page edit preview action group are promoted in light/dark mode; signed modal and expired-preview captures remain runner-tooling follow-ups. | Future | S | Med | §1, §5 |
Shipped: Rewrite summary + composer description — evidence: capell.json marketplace summary and package description plus composer.json description now lead with unsaved live-theme previews and no persistence. | Done | S | Med | §5 |
Shipped: Align Cache-Control literal between controller and test — evidence: controller and route assertions use no-store, private. | Done | S | Med | §4 |
Shipped: Remove dead PagePreviewSnapshotData::$path — evidence: DTO and snapshot creation no longer contain or write the field. | Done | S | Med | §2 |
Shipped: Add CHANGELOG.md + LICENSE (proprietary) — evidence: both package-root files exist and the changelog records this cleanup. | Done | S | Med | §4 |
Shipped 2026-06-08: formalize the layout-builder ↔ filament-peek action contract with StoresLayoutBuilderPreviewState. | Done | M | High | §4 |
Shipped: Extract find()/snapshot read into FindPagePreviewSnapshotAction | Done | M | Med | §2 |
Shipped: De-dup currentUser()/cacheStore()/ttlMinutes() into ResolvesPreviewContext | Done | S | Med | §2 |
| Shipped 2026-06-08: expose responsive/device-frame preview through package config and upstream Peek modal config. | Done | M | High | §2, §3 |
| Shipped 2026-06-08: “Unsaved preview” ribbon on private preview responses. | Done | M | Med | §2 |
| Shipped 2026-06-06: Snapshot payload size guard + log | Done | S | Med | §2 |
| Shipped 2026-06-08: backfill render-path tests for render-failure 500 responses, social-image overlay, ribbon output, responsive config, and the typed preview-state contract. | Done | M | High | §4 |
| Shareable read-only preview token (reconcile with publishing-studio) | Later | L | High | §3 |
| Live/auto-refresh preview + upstream builder-editor mode | Later | L | Med | §3 |
| Generalize preview to non-Page pageables | Later | M | Med | §3 |
| Workspace-context and multilingual preview branch coverage | Later | M | Med | §4 |
| Preview-render query/performance budget + assertion | Later | M | Med | §2, §4 |