Guide

Publish Workflows architecture

Bootstrap: includes/class-bootstrap.php. Feature modules live under includes/ and admin React apps under includes/admin/.

Modules

ModuleRole
taxonomy/class-workflow-status.phpPrivate workflow-status taxonomy; seeds terms; fires prc_publish_workflows_workflow_status_changed
watchers/class-watchers.phpprc_watchers meta, watchers + watchChildPosts REST fields, POST …/watch-self
notifications/class-notifications.phpAsync Slack-only DMs via Action Scheduler
workspaces/workspace CPT, REST, tracker panels, MCP search abilities (search-workspaces, get-workspace)
checklist/Publication checklist engine, first-party checks, editor plugin
admin/Status Board, Calendar, Workspaces, dashboard widget, editor SlotFills
user-profile/class-slack-user-id.phpprc_slack_user_id profile field and Connect Slack button

Workflow status

  • Taxonomy is private, REST-exposed at /wp/v2/workflow-status, hidden from the default taxonomy meta box.
  • Registration runs at init 11 from Watchers::get_post_types(). Types that opt in later (email campaigns at init 20) are attached at init 99 so REST can persist the term.
  • Editor UI uses PluginPostStatusInfo and writes editPost({ 'workflow-status': [termId] }).
  • Becoming publish clears the term (Status Board, Gutenberg REST, WP-CLI, and futurepublish). rest_pre_insert_{post_type} empties the REST assignment so Gutenberg cannot write the leftover term back after wp_insert_post. Clearing that leftover term does not notify as a move to draft; the publish hop keeps the prior stage for watchers.
  • Final draft → publish also rides prc_platform_async_on_publish / prc_platform_async_on_unpublish.

Default post type for taxonomy + watchers is post, filterable with prc_publish_workflows_post_types. Other CPTs opt in with add_post_type_support( $type, 'prc-publish-workflows' ).

Watch child posts

Posts with multiSectionReport meta (report packages) can set prc_watch_child_posts. The editor writes this through the watchChildPosts REST field (registered before watchers so combined saves persist the toggle first). When enabled, Watchers::mirror_parent_watchers_to_children() adds or removes only the current user on each chapter postId — not the full parent watcher list. Auto-add via POST …/watch-self and chapter-meta changes use the same mirror path. Toggle-off skips child writes on that save; it does not retroactively clear chapter watchers.

Workspaces

  • CPT workspace: non-public UI, show_in_rest, supports title, excerpt, author, custom fields, and prc-publish-workflows (so watchers meta applies).
  • Items meta prc_workspace_items: { id, type, objectId, url, label, addedBy, addedAt } with type of post, attachment, or link. Not exposed via core REST; all writes go through the custom controller.
  • Tracker project refs prc_workspace_linear_project and prc_workspace_asana_project: { id, name, url } or empty. Not REST-exposed. Workspace_Trackers owns persist/get. Linear_Tracker (GraphQL) and Asana_Tracker (REST) fetch live lists (max 50). Routes live in Workspaces_Trackers_REST_Controller.
  • Uploaded media store _prc_workspace_id for traceability.
  • MCP abilities prc-publish-workflows/search-workspaces and prc-publish-workflows/get-workspace search title, description, and collected items (labels, filenames, URLs). REST GET /workspaces?search= stays title/excerpt-only.

Build bundles

BundleConsumer
build/watchers-panel/Watchers sidebar
build/post-status-info/Workflow badge in Status & visibility
build/status-board/Status Board admin
build/calendar/Calendar admin
build/dashboard-widget/Posts in production widget
build/checklist/Publication checklist panel + sidebar
build/workspaces/Workspaces DataViews app

Hooks (summary)

HookKindPurpose
prc_publish_workflows_post_typesFilterPost types for taxonomy + watchers
prc_publish_workflows_status_pipelineFilterStatus Board column definitions
prc_publish_workflows_workflow_status_changedActionStage change
prc_publish_workflows_should_notifyFilterSuppress notifications
prc_publish_workflows_register_checksActionRegister checks with register_check()
prc_publish_workflows_block_on_failingFilterBlock publish when a check is incomplete
prc_publish_workflows_checklist_enabled_typesFilterPost types for the prc_checklist REST field
prc_publish_workflows_publish_authorizer_rolesFilterRoles that may publish any type
prc_publish_workflows_editor_may_publish_typesFilterTypes the Editor role may also publish. Default short-read, prc_email_campaign, prc_email_txn
prc_publish_workflows_notification_slack_messageFilterSlack DM body

Publication checklist

The engine lives in includes/checklist/. Publication_Checklist holds a registry of checks. Sibling plugins and blocks call PRC\Platform\Publish_Workflows\register_check() on prc_publish_workflows_register_checks (fires on plugins_loaded). Each check returns a Status (complete, incomplete, or info).

Results are exposed on enabled post types as the readonly REST field prc_checklist. The editor plugin (build/checklist/) reads that field and renders a pre-publish panel plus a sidebar. Replace one row with addFilter( 'prc-publish-workflows.checklist.item.{check_id}', ... ).

prc_publish_workflows_block_on_failing is off by default. When it is on, incomplete checks lock the publish button and rest_pre_insert_{type} / wp_insert_post_data refuse a change to a published status.

Publish permission is a separate always-on gate. Administrators and producers can confirm Ready to publish in the pre-publish panel. Editors can do that for short-read, prc_email_campaign, and prc_email_txn. Designers can do that for chart. Other roles cannot toggle the control. REST returns prc_publish_not_authorized (403) if they try to publish anyway. Already-published updates and cron go-lives are not blocked.

Was this helpful?