Skip to content

Filament Peek — Improvement & Growth Plan

Package: capell-app/filament-peek · Kind: package · Tier: free · Product group: Capell Foundation · Bundle: foundation · Status: Complete

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.

  • Done / Shipped: remove the dead path field from the snapshot. — Evidence: PagePreviewSnapshotData now contains only token/user/page/form/workspace/Layout Builder state, CreatePagePreviewSnapshotAction no longer writes path, and rg "path:" packages/filament-peek/src packages/filament-peek/tests finds no snapshot-path assignment. — src/Data/PagePreviewSnapshotData.php, src/Actions/CreatePagePreviewSnapshotAction.phpS
  • Done / Shipped: move snapshot read into a dedicated action. — Evidence: FindPagePreviewSnapshotAction owns cache-key lookup and DTO hydration, while PagePreviewController, PeekPagePreviewActionTest, and SnapshotActionTest call FindPagePreviewSnapshotAction::run($token) instead of reading through the create action. — src/Actions/FindPagePreviewSnapshotAction.php, src/Http/Controllers/PagePreviewController.php, tests/Unit/SnapshotActionTest.phpM
  • Done / Shipped: de-duplicate preview context helpers. — Evidence: CreatePagePreviewSnapshotAction, FindPagePreviewSnapshotAction, and StoreLayoutBuilderPreviewStateAction all use Concerns\ResolvesPreviewContext for 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.phpS
  • Done / Shipped: device/responsive preview controls. — Capell now bridges capell-filament-peek.preview.device_presets into the upstream filament-peek.devicePresets modal 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.phpM
  • 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-store and X-Robots-Tag headers. 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.phpM
  • Cap snapshot payload size.formState and layoutBuilderState.containers are 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 before Cache::put. — src/Actions/CreatePagePreviewSnapshotAction.php, src/Actions/StoreLayoutBuilderPreviewStateAction.phpS
  • Reconcile capell.json adminQueryBudget: 40 with the real render cost.RenderPagePreviewSnapshotAction::renderSnapshot() eager-loads ~12 relations and may issue additional find() queries in resolveSite/resolveLayout/previewTranslations (per-language Language::find) and RegisterLayoutBuilderPreviewWidgetsAction (widgets + assets + target models). The 40-query admin budget likely doesn’t reflect the frontend render path (whose frontendRenderBudgetMs is 0/unset). Add a budget that covers the preview render and a regression test asserting query count. — capell.json, src/Actions/RenderPagePreviewSnapshotAction.phpM

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’s WorkspacePeekPreviewAction + GenerateWorkspacePreviewUrlAction (packages/publishing-studio/.../Actions/WorkspacePeekPreviewAction.php), which already mints workspace preview URLs via the same upstream Peek modal and open-preview-modal event. 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_until are copied into pageAttributes() 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) previewpboivin/filament-peek ships 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 pageablesRenderPagePreviewSnapshotAction is hard-bound to Capell\Core\Models\Page; StoreLayoutBuilderPreviewStateAction already accepts the broader Pageable contract, so the create/render side is the only thing blocking previewing other pageable models. Gap.
  • 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(), the open-preview-modal event — see PeekPagePreviewAction.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, upstream pboivin/filament-peek availability, and configured preview cache-store reachability; FilamentPeekHealthCheckTest covers 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 invoking handle() or clear(). This locks the handle/clear signatures 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 update ability everywhere — correct, but double-edged. — Both PeekPagePreviewAction (->authorize(... Gate::allows('update', $record))) and RenderPagePreviewSnapshotAction::renderSnapshot() (Gate::authorize('update', $page)) gate on update. 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: PagePreviewRouteTest now rejects validly-signed preview URLs for unauthenticated requests and for authenticated non-admin snapshot owners before the preview renderer output appears; PagePreviewController treats 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-Control literal 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 only en translations ship. Localize the lang attribute 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.md and proprietary LICENSE are present. — Evidence: CHANGELOG.md tracks the Filament Peek cleanup work under Unreleased, and LICENSE now 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-language currentTranslation selection, and query-count/performance budget. — tests/

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.json summary was rewritten. — Evidence: capell.json now 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 description was rewritten. — Evidence: composer.json now 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.json now 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.
ItemBucketEffortImpactSection 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.DoneSHigh§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.DoneSHigh§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.FutureSMed§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.DoneSMed§5
Shipped: Align Cache-Control literal between controller and test — evidence: controller and route assertions use no-store, private.DoneSMed§4
Shipped: Remove dead PagePreviewSnapshotData::$path — evidence: DTO and snapshot creation no longer contain or write the field.DoneSMed§2
Shipped: Add CHANGELOG.md + LICENSE (proprietary) — evidence: both package-root files exist and the changelog records this cleanup.DoneSMed§4
Shipped 2026-06-08: formalize the layout-builder ↔ filament-peek action contract with StoresLayoutBuilderPreviewState.DoneMHigh§4
Shipped: Extract find()/snapshot read into FindPagePreviewSnapshotActionDoneMMed§2
Shipped: De-dup currentUser()/cacheStore()/ttlMinutes() into ResolvesPreviewContextDoneSMed§2
Shipped 2026-06-08: expose responsive/device-frame preview through package config and upstream Peek modal config.DoneMHigh§2, §3
Shipped 2026-06-08: “Unsaved preview” ribbon on private preview responses.DoneMMed§2
Shipped 2026-06-06: Snapshot payload size guard + logDoneSMed§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.DoneMHigh§4
Shareable read-only preview token (reconcile with publishing-studio)LaterLHigh§3
Live/auto-refresh preview + upstream builder-editor modeLaterLMed§3
Generalize preview to non-Page pageablesLaterMMed§3
Workspace-context and multilingual preview branch coverageLaterMMed§4
Preview-render query/performance budget + assertionLaterMMed§2, §4