# Publishing Studio — Improvement & Growth Plan

> Package: capell-app/publishing-studio · Kind: package · Tier: premium · Product group: Capell Publishing Pro · Bundle: publishing-pro · Status: Complete

## 1. Snapshot

Publishing Studio is Capell's flagship editorial-workflow package: copy-on-write workspaces (drafts) over any registered `Draftable` model, atomic versioned publish, rebase/stale-detection, rollback + entity restore, signed live preview links, an approval chain, a durable scheduler (publish / unpublish / embargo / review-reminder events), an editorial calendar that aggregates contributors (Blog, Campaign Studio, Newsletter, Events), and four admin pages (Workflow command centre, Activity trail, Scheduled publishing, Stale drafts) plus two Filament resources (`WorkspaceResource`, `PreviewLinkResource`) and several dashboard widgets/Livewire panels. It is large and mature: **242 `src/` PHP files, 132 test files**, surfaces `["admin","console"]`. Core engine lives in root-namespaced classes (`Publisher`, `Rebaser`, `Rollback`, `ReleaseWindowGuard`) plus ~45 Actions; data models are `Workspace`, `Version`, `PublishingRevision`, `PreviewLink`, `SchedulerEvent`, `SchedulerDelivery`, `SchedulerIcalToken`, `WorkspaceApproval`, `WorkspaceFieldComment`, `WorkspaceReviewAssignment` over 12 migrations (`workspaces`, `versions`, `preview_links`, `publishing_revisions`, `publishing_scheduler_events`, approvals/comments/assignments, plus `workspace_id`/`shadowed_by_workspace_id` columns added to core + external tables). Dependencies: `capell-app/{admin,core,html-cache,migration-assistant,navigation}` + `jfcherng/php-diff`. Marketplace summary now leads with the review-first CMS value proposition, and the manifest declares the extension card plus populated editor/reviewer workflow captures. Empty workflow, preview-link, scheduled-publishing, and stale-draft captures were demoted until seeded recapture.

## 2. Improvements (existing functionality)

Prioritized.

- **Closed 2026-06-05: Co-locate publish-check + release-window config defaults inside the package.** — Shipped `config/publishing-studio.php`, merged it as `capell.publishing-studio`, published it for console installs, and included default publish checks, release-window settings, scheduled publishing, prune schedule, and preview home-route config. — `config/publishing-studio.php`, `src/Providers/PublishingStudioServiceProvider.php` — M
- **Done/Shipped: Make the manifest health check actually verify something.** `PublishingStudioHealthCheck` now reports real diagnostics for required workflow/revision/scheduler tables and configured publish-readiness checks, with focused health coverage. — `src/Health/PublishingStudioHealthCheck.php`, `tests/Feature/Health/PublishingStudioHealthCheckTest.php` — M
- **Closed 2026-06-05: Fix the `load-testing` capability so it works on a real install.** `LoadTestPublishingStudioCommand` now resolves a package-owned fixture model from `src/Support/LoadTesting`, so the advertised command no longer depends on `tests/` autoload. — `src/Console/Commands/LoadTestPublishingStudioCommand.php`, `src/Support/LoadTesting/WorkspaceDraftableFixture.php` — S
- **Done/Shipped: `broken-links` is clarified as internal link integrity.** The legacy identifier remains `broken-links` for config compatibility, but the surfaced label and messages now say "Internal link integrity" and explicitly validate only internal Capell URLs against `page_urls`; external URLs are ignored until a future opt-in HTTP probe exists. — `src/Checks/BrokenLinkCheck.php`, `resources/lang/en/workspace.php`, `tests/Integration/Checks/IndividualChecksTest.php` — M
- **Done/Shipped: legacy scheduled-publish job is a durable scheduler shim.** `PublishScheduledPublishingStudioJob` no longer runs a separate Workspace scan-and-publish loop. It delegates to `RunDueSchedulerEventsAction::run(includePublish: true)`, so publish, unpublish, and reminder execution all flow through claimed `SchedulerEvent` rows and `ExecuteSchedulerEventAction`. — `src/PublishScheduledPublishingStudioJob.php`, `src/SchedulePublishAction.php`, `tests/Integration/PublishScheduledPublishingStudioJobTest.php` — M
- **Done/Shipped: central publish-readiness data boundary.** `BuildPublishReadinessAction` returns `PublishReadinessData` from the dry-run report, URL collisions, stale conflicts, and publish-check failures. `ValidateAction` now consumes that typed boundary instead of interpreting `DryRunReport` directly, and dry runs can respect release-window bypasses for authorized callers. — `src/Actions/BuildPublishReadinessAction.php`, `src/Data/PublishReadinessData.php`, `src/Publisher.php`, `src/Filament/Resources/PublishingStudio/Actions/ValidateAction.php`, `tests/Integration/PublisherDryRunTest.php` — M
- **Closed 2026-06-05: Populate the CHANGELOG.** The changelog now records marketplace value-copy/media expansion, real health diagnostics, manifest settings/table reconciliation, and the production-autoloadable load-test fixture. — `CHANGELOG.md` — S

## 3. Missing Features (gaps)

Tied to `capabilities[]` and editorial-workflow norms.

- **Field-level merge / three-way diff resolution (differentiator).** `Rebaser` surfaces conflicting uuids and supports only coarse `keep-mine` / `take-live` per record; there is no field-level merge when two editors touch different fields of the same record. Statamic/Sanity-class tools resolve at field granularity. This is the single biggest differentiator available given the diff infrastructure (`WorkspaceDiffService`, `jfcherng/php-diff`) already exists. — `src/Rebaser.php`, `src/Services/WorkspaceDiffService.php`
- **Scheduled-content calendar drag-to-reschedule (table stakes for "editorial calendar").** The calendar (`ContentSchedulerCalendarWidget`, `BuildEditorialCalendarEventsAction`) aggregates and displays events but reschedule still goes through `SchedulerMetadataAction` forms. Inline drag-reschedule is expected of an editorial calendar.
- **Approval workflow configurability beyond numeric levels.** `Workspace::approve()` uses `settings.requiredApprovalLevels` (default 2) — purely a count. Norms include named stages, per-role required approvers, and per-content-type policies. `ReviewPolicyResolver`/`RequiredReviewer` exist but the public surface is a bare integer. — `src/Models/Workspace.php`, `src/Approvals/ReviewPolicyResolver.php`
- **Multi-site / cross-site coordinated publish.** Release Workspaces group content for one atomic publish, but `siteId()` derivation in `SyncWorkspaceSchedulerEventsAction` picks the _first_ page's site; there is no first-class "publish this release to sites A+B" concept. Given Capell is multi-site, this is a notable gap. — `src/Actions/SyncWorkspaceSchedulerEventsAction.php`
- **Notifications breadth.** Only `WorkspaceStateNotification` and `WorkspaceReviewReminderNotification` exist. Editorial teams expect Slack/Teams/webhook fan-out and digest summaries ("3 drafts awaiting your approval"). Could cross-sell into a notifications/automation package.
- **Embargo on the _public_ read path.** `embargo_until` blocks publish, but there is no evidence of a public-render guard that hides embargoed-but-scheduled content if a stale cache or preview leaks it. Worth an explicit anonymous-safety test asserting embargoed content never renders to guests.
- **Diff/restore for media and layout blocks.** `MediaDiffService` exists, but entity restore (`Rollback/EntityRollbackAction`) is row-oriented; granular per-asset restore and layout-block-level rollback would round out `entity-restore`.

## 4. Issues / Risks

- **Closed 2026-06-05: Publish checks may be silently no-op (highest risk).** Default config now resolves to the shipped publish checks, and `PublishCheckPipelineTest` asserts the configured defaults are non-empty, implement `PublishCheck`, resolve through the container, and return pipeline results. — `src/Checks/PublishCheckPipeline.php`, `tests/Integration/PublishCheckPipelineTest.php`
- **Closed 2026-06-05: Manifest ↔ code drift.** `capell.json` now declares `PublishingStudioSettings`, `database.settings: true`, the package-owned storage table set, the extension card, desktop/mobile hero images, and runner-backed workflow captures. `load-testing` now resolves a production-autoloadable fixture model. — `capell.json`, `src/Settings/PublishingStudioSettings.php`, `docs/screenshots.json`, `src/Support/LoadTesting/WorkspaceDraftableFixture.php`
- **Done/Shipped: admin query budget is asserted for dashboard and calendar Actions.** `AdminQueryBudgetTest` reads `performance.adminQueryBudget` from `capell.json` and profiles the workflow command center, site stats, workspace activity, merge history, scheduler event builder, and editorial calendar builder against the 40-query ceiling. — `capell.json`, `tests/Feature/Performance/AdminQueryBudgetTest.php`
- **`frontendRenderBudgetMs: 0` vs a real frontend surface.** The package _does_ serve frontend HTTP (signed preview render path through `ResolveWorkspaceContext`, plus the exit-preview route), yet `surfaces` omits `"frontend"` and the render budget is 0. Preview rendering disables cache (`config(['capell-core.disable_cache' => true])`) so it is inherently slower than a cached page; document/measure the preview render path rather than declaring 0. — `capell.json`, `src/Http/Middleware/ResolveWorkspaceContext.php`
- **Concurrency: strong, keep it tested.** `Publisher` row-locks the live `Version` and re-checks for a shifted live id (throws `StaleWorkspaceException`); `ExecuteSchedulerEventAction` claims events via `lockForUpdate` + a 15-minute stale-claim reclaim; SQLite path is documented as serialised. `PublisherConcurrencyTest`, `PublisherEmbargoTest`, `PublisherReleaseWindowTest`, `PublishScheduledPublishingStudioJobTest` cover this. Risk is low — preserve these tests on any scheduler refactor.
- **Closed 2026-06-05: Public-output safety explicit coverage.** Preview is signed (`URL::temporarySignedRoute`) with a DB-backed revocable token as the authoritative key, signed `cms_workspace` cookie (`hash_equals`), `userMayResolve()` re-checks `view` policy so revoked users lose context, and workspace responses set `Cache-Control: private, no-store`. The live frontend test now proves an anonymous request to the live URL keeps rendering published copy while hiding embargoed draft copy, workspace names, preview markers, workspace cookies, and preview-only private/no-store headers. — `src/Http/Middleware/ResolveWorkspaceContext.php`, `src/Actions/GenerateWorkspacePreviewUrlAction.php`, `src/Http/Controllers/SchedulerIcalFeedController.php`, `tests/Feature/Page/PageWorkspaceDraftTest.php`
- **Scheduler `siteId` heuristic.** `SyncWorkspaceSchedulerEventsAction::siteId()` takes the first workspace-scoped page's `site_id`; for a multi-site release workspace this is ambiguous and can misattribute calendar/feed scoping. — `src/Actions/SyncWorkspaceSchedulerEventsAction.php`
- **i18n.** Labels are translation-key driven (`capell-publishing-studio::*`) — good. Confirm scheduler/notification human strings and exception messages (several are hard-coded English in `Publisher`/`Rebaser`, e.g. "Workspace #%d must be approved…") are acceptable as developer-facing only, not user-facing. — `src/Publisher.php`, `src/Rebaser.php`
- **Test-coverage gaps.** Config-default publish-check resolution, embargo/live-path safety, and `LoadTestPublishingStudioCommand` production-style autoload coverage are now present. Multi-site release scoping remains the notable missing dedicated test. Coverage is otherwise broad.

## 5. Marketplace & Selling

**Critique.** The current `summary` and composer `description` are accurate but read as a feature list, not a value proposition — they enumerate verbs (preview/compare/approve/schedule/publish/restore/rollback) without naming the pain (publishing directly to live, no review trail, no safe rollback). The composer `description` ("Editorial workflow package for Capell revisions, scheduling, approvals, and controlled publishing") is serviceable but generic.

**Improved 1-sentence summary.**

> Publishing Studio turns Capell into a review-first CMS: edit any content in an isolated draft workspace, get sign-off, schedule it, and publish atomically — with one-click rollback when something breaks.

**Improved 3–4 sentence description.**

> Publishing Studio is Capell's premium editorial workflow. Every change happens in a copy-on-write workspace that stays invisible to visitors until it's approved and published as a single atomic, versioned release — so the live site is never half-edited. Built-in publish-readiness checks (accessibility, SEO meta, alt text, internal links, URL collisions, stale-draft and release-window guards) stop risky publishes before they ship, while signed, revocable preview links let stakeholders review the exact pending state. When a publish goes wrong, roll back to any prior version or restore individual entities in seconds.

**Screenshot / media status.** The manifest now exposes the extension card plus the populated editor/reviewer workflow captures. Empty workflow dashboard, preview-link, scheduled-publishing, and stale-draft captures were demoted. The contract still asks for additional route/admin captures for live preview, compare/readiness, approval history, scheduler metadata, activity history, rollback/restore, and preview banner states; a 20-30s capture of the draft-to-approve-to-schedule-to-publish-to-rollback loop would still strengthen the listing.

**Pricing / tier / bundle positioning.** Correctly `premium` in the `publishing-pro` bundle — this is anchor-tenant functionality, not an add-on. Position it as the headline of the Publishing Pro bundle. Natural cross-sell follows the dependency + `supports` graph: `migration-assistant` (recovery/import into workspaces), `html-cache` (publish-time invalidation), and the soft contributors — `blog`, `campaign-studio`, `newsletter`, `events` — which feed the editorial calendar. Bundle messaging: "buy Publishing Pro, get a governed editorial calendar across every content type you publish."

**Differentiators / value props / target buyer.** Differentiators: atomic multi-model release workspaces, durable scheduler with embargo + release windows, signed revocable previews, and true version rollback + entity restore — capabilities usually reserved for Statamic/Sanity/Contentful-tier products. Target buyer: agencies and in-house teams running multi-author, compliance-sensitive, or brand-controlled sites where publishing directly to production is unacceptable.

**Keywords/tags (8–12).** editorial-workflow, content-approval, draft-publishing, scheduled-publishing, content-versioning, rollback, live-preview, release-windows, publish-readiness, editorial-calendar, content-governance, workspaces.

## 6. Prioritized Roadmap

| Item                                                                                                                                                                  | Bucket | Effort | Impact | Section ref                                                                                                                                                                                             |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ship package config with publish-check + release-window defaults (`mergeConfigFrom`)                                                                                  | Done   | M      | High   | §2, §4                                                                                                                                                                                                  |
| Test asserting default config resolves ≥1 publish check (guard against silent no-op)                                                                                  | Done   | S      | High   | §4                                                                                                                                                                                                      |
| Done/Shipped: Reconcile manifest with code: declares settings, package-owned storage tables, expanded screenshots[], and production-autoloadable load-testing fixture | Done   | S      | High   | §4                                                                                                                                                                                                      |
| Done/Shipped: Anonymous-safety test proves guests never see workspace markers or embargoed content on live path                                                       | Done   | S      | High   | §4                                                                                                                                                                                                      |
| Done/Shipped: Populate CHANGELOG with real release history                                                                                                            | Done   | S      | Med    | §2                                                                                                                                                                                                      |
| Done/Shipped: Give the manifest health check real assertions (tables and checks resolvable)                                                                           | Done   | M      | Med    | §2                                                                                                                                                                                                      |
| Done/Shipped: Fix `load-test` fixture to a production-autoloadable model                                                                                              | Done   | S      | Med    | §2, §4                                                                                                                                                                                                  |
| Consolidate legacy publish job onto durable scheduler events                                                                                                          | Done   | M      | Med    | §2 — closed 2026-06-08: `PublishScheduledPublishingStudioJob` delegates to `RunDueSchedulerEventsAction::run(includePublish: true)`, so scheduled publishing now uses durable claimed scheduler events. |
| Central `BuildPublishReadinessAction` returning typed Data                                                                                                            | Done   | M      | Med    | §2 — closed 2026-06-08: `BuildPublishReadinessAction` returns `PublishReadinessData`, and `ValidateAction` consumes it for failure/warning/success notifications.                                       |
| Clarify/extend `broken-links` (rename or add opt-in external probe)                                                                                                   | Done   | M      | Med    | §2, §3 — closed 2026-06-08: surfaced copy now says "Internal link integrity"; the legacy `broken-links` identifier remains for config compatibility, and external URLs are explicitly ignored.          |
| Profile dashboard/calendar Actions against `adminQueryBudget: 40`; assert query ceiling in tests                                                                      | Done   | M      | Med    | §4 — closed 2026-06-08: `AdminQueryBudgetTest` profiles workflow, dashboard, scheduler, and editorial-calendar Actions against the manifest budget.                                                     |
| Recapture populated Publishing Studio workflow, preview-link, scheduled-publishing, and stale-draft screenshots before promoting them as product media                | Later  | S      | Med    | §5 — media-only follow-up; implementation scope is complete and promoted screenshots should wait for populated runner captures.                                                                         |
| Field-level three-way merge in Rebaser (flagship differentiator)                                                                                                      | Later  | L      | High   | §3                                                                                                                                                                                                      |
| Configurable named approval stages / per-role required approvers                                                                                                      | Later  | L      | Med    | §3                                                                                                                                                                                                      |
| First-class multi-site coordinated release publish + correct calendar site scoping                                                                                    | Later  | L      | Med    | §3, §4                                                                                                                                                                                                  |
| Calendar drag-to-reschedule + Slack/webhook + digest notifications                                                                                                    | Later  | L      | Med    | §3                                                                                                                                                                                                      |
| Granular media/layout-block restore                                                                                                                                   | Later  | L      | Low    | §3                                                                                                                                                                                                      |