Skip to content

feat(emails): unified email registry + DataViews list#4727

Open
kmwilkerson wants to merge 7 commits into
trunkfrom
nppd-945-unified-emails-newspack-slice
Open

feat(emails): unified email registry + DataViews list#4727
kmwilkerson wants to merge 7 commits into
trunkfrom
nppd-945-unified-emails-newspack-slice

Conversation

@kmwilkerson
Copy link
Copy Markdown

@kmwilkerson kmwilkerson commented May 14, 2026

What this PR does

First slice of the unified email management UI (NPPD-945). Replaces the existing WizardsActionCard-based email list at Newspack → Settings → Emails with a DataViews-based list, backed by a new curated email registry in PHP.

image

This slice surfaces only Newspack-registered emails. WooCommerce email surfacing is deliberately deferred to follow-up PRs to keep the diff scoped and the engineering risk concentrated where it belongs.

Code changes

Backend (includes/wizards/newspack/class-emails-section.php)

  • Adds Emails_Section::get_email_registry() — a curated registry of 23 transactional emails, keyed by stable slug. Each entry includes source, newspack_type or woo_email_id, recommended (bool), plugin_dependency, recipient, label, and trigger_description.
  • Extends api_get_email_settings() to return a newspack_emails array — existing Newspack emails joined against the registry, with metadata for recipient labeling and trigger descriptions. Emails not in the registry still appear with sensible defaults.
  • Always registers the GET route; the WC editor PUT route remains conditional on WooCommerce being active.

Frontend (src/wizards/newspack/views/settings/emails/)

  • Replaces the WizardsActionCard.map() with a DataViews list, following the pattern from src/wizards/audience/views/content-gates/institutions/index.tsx.
  • Full-width layout (WizardsTab className="newspack-emails-tab").
  • Grid view by default, table view available via the layout toggle.
  • Columns / card content: Email (label + trigger description), Recipient, Status, Preview placeholder, Actions (kebab).
  • Status indicator: small filled green dot (Enabled) / outlined gray dot (Disabled) alongside the status text.
  • Status filter (DataViews secondary filter): Enabled / Disabled.
  • Search across all rows.
  • Kebab actions on every row: Edit (always), Activate/Deactivate (reader-revenue emails only), Reset (receipt/welcome templates only).
  • Single-view layout — all managed emails listed in one view, no "Show all" toggle.
  • New stylesheet emails.scss: aligns DataViews controls (matching src/wizards/audience/views/integrations/style.scss pattern), styles status dots, trigger description color, preview placeholder tile.

Preview placeholder

The preview field renders an envelope icon on a neutral gray tile. A // TODO: Replace with <EmailPreview> component when built comment marks where a real preview component would go. Building that component is out of scope for this PR.

Tests

  • PHPUnit (tests/unit-tests/emails-section.php): registry entry counts, recommended counts, plugin-dependency counts, label/trigger/recipient completeness.
  • Jest (src/wizards/newspack/views/settings/emails/emails.test.js): DataViews renders with mock data, recipient column resolves, status enabled/disabled, subtitle absence, tabs/show-all absence.

Why slice this PR

The full PRD scope (Newspack + WooCommerce + WC Subscriptions email surfacing, with plugin-conditional logic, status toggling across different storage backends) is meaty. Splitting along the registry boundary keeps each PR independently shippable and reviewable, and concentrates the trickier Woo-discovery logic in follow-up PRs where it can get focused attention.

Manual testing

Tested locally with Newspack, Newspack Newsletters, WooCommerce, and WooCommerce Subscriptions activated. Verified:

  • DataViews list renders with all expected Newspack emails in grid view by default
  • Layout toggle switches between grid and table views
  • Status filter chip toggles Enabled / Disabled rows correctly
  • Search filters across all rows
  • Edit action opens the existing email editor
  • Activate/Deactivate updates email status and persists across refresh
  • Reset action preserved for receipt and welcome
  • The enable_woocommerce_email_editor toggle (separate section below) still works untouched

PHPUnit not run locally.

Note: pre-existing TypeScript generic variance errors on the DataViews wrapper types are not introduced by this PR.

All Submissions:

kmwilkerson and others added 3 commits May 12, 2026 15:34
The joshtronic/php-loremipsum package source moved from GitHub to
git.sherver.org. Same version (2.1.0), same commit hash. The dist
and support blocks referencing old GitHub URLs were dropped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e 1)

First slice of the unified email management UI (NPPD-945). This PR:

- Adds `Emails_Section::get_email_registry()` with 23 curated entries
  covering Newspack and WooCommerce transactional emails, with metadata
  for default-view filtering and plugin-conditional surfacing.
- Extends `api_get_email_settings()` to return enriched `newspack_emails`
  joined against the registry.
- Replaces the existing WizardsActionCard list with a DataViews list
  following the institutions view pattern. Adds a "Show all emails" link
  to reveal lower-priority entries.
- Preserves the existing Reset action (receipt/welcome only) and inactive-
  email notification copy.
- Does not yet merge WooCommerce email surfacing into the unified view —
  that's a follow-up. The Woo block editor toggle remains a separate section.

Known follow-ups (out of scope for this PR):
- Re-add `login-otp-oauth` registry entry once the OAuth OTP email type is
  registered in `Reader_Activation_Emails::EMAIL_TYPES`.
- PRD rationale for `woo-processing-order`, `woo-completed-order`, and
  `woo-on-hold-order` should be updated; these are customer-facing, not
  admin-facing. See TODO comments inline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Drop tabs and show-all toggle; list all 24 emails in single view sorted by category
- Add recipient column (Reader/Admin)
- Move email actions (Edit/Activate/Deactivate/Reset) into kebab menu on every row
- Add secondary status filter (Enabled/Disabled)
- Restore inactive-email notification copy via Note column
- Add Edit template button and page subtitle
- Use trigger_description from registry as sub-text under email title
- Various polish: button variants, color overrides, fixed stale-closure bugs
@kmwilkerson kmwilkerson changed the title feat(emails): unified email registry + DataViews list (NPPD-945, slice 1) feat(emails): unified email registry + DataViews list May 14, 2026
@kmwilkerson kmwilkerson marked this pull request as ready for review May 14, 2026 17:20
@kmwilkerson kmwilkerson requested a review from a team as a code owner May 14, 2026 17:20
@chickenn00dle chickenn00dle added the [Status] Needs Review The issue or pull request needs to be reviewed label May 14, 2026
@chickenn00dle chickenn00dle requested a review from Copilot May 14, 2026 17:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the first slice of a unified email management experience for Newspack Settings by replacing the existing card-based email list with a DataViews-powered list backed by a curated PHP email registry.

Changes:

  • Adds a curated email registry and enriches the Emails settings REST response with registry metadata.
  • Replaces the Emails settings UI with a searchable/filterable DataViews grid/table.
  • Adds PHPUnit and Jest coverage for registry basics and list rendering.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
includes/wizards/newspack/class-emails-section.php Adds the registry, always registers the GET route, and enriches/sorts Newspack email response data.
src/wizards/newspack/views/settings/emails/index.tsx Updates the Emails tab wrapper for the new full-width DataViews layout.
src/wizards/newspack/views/settings/emails/emails.tsx Replaces action cards with DataViews, fetches email data, and wires edit/status/reset actions.
src/wizards/newspack/views/settings/emails/emails.scss Adds styling for the DataViews layout, preview placeholder, descriptions, and status indicators.
src/wizards/newspack/views/settings/emails/emails.test.js Adds Jest tests for basic DataViews rendering and visible row metadata.
tests/unit-tests/emails-section.php Adds PHPUnit tests for registry counts and required registry fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

function Emails() {
return (
<WizardsTab title={ __( 'Emails', 'newspack-plugin' ) }>
<WizardsTab className="newspack-emails-tab">
Comment on lines +73 to +75
apiFetch< EmailSettings >( {
path: '/newspack/v1/wizard/newspack-settings/emails',
} )
Comment on lines +92 to +122
const updateStatus = useCallback(
( postId: number, status: string ) => {
setError( null );
// Optimistic update.
setData( prev =>
prev.map( email => {
if ( email.post_id === postId ) {
return { ...email, status };
}
return email;
} )
);
apiFetch( {
path: `/wp/v2/${ postType }/${ postId }`,
method: 'POST',
data: { status },
} ).catch( () => {
// Revert on failure.
setData( prev =>
prev.map( email => {
if ( email.post_id === postId ) {
return {
...email,
status: status === 'publish' ? 'draft' : 'publish',
};
}
return email;
} )
);
setError( __( 'Failed to update email status.', 'newspack-plugin' ) );
} );
Comment on lines +127 to +139
const resetEmail = useCallback(
( postId: number ) => {
setError( null );
apiFetch( {
path: `/newspack/v1/wizard/newspack-audience-donations/emails/${ postId }`,
method: 'DELETE',
} )
.then( () => {
fetchData();
} )
.catch( () => {
setError( __( 'Failed to reset email. Please try again.', 'newspack-plugin' ) );
} );
Comment on lines +315 to +330
foreach ( $emails as $type => $email ) {
if ( isset( $registry_lookup[ $type ] ) ) {
$match = $registry_lookup[ $type ];
$email['recommended'] = $match['recommended'];
$email['view_category'] = $match['recommended'] ? 'essentials' : 'all-enabled';
$email['trigger_description'] = $match['trigger_description'];
$email['registry_slug'] = $match['registry_slug'];
$email['recipient'] = $match['recipient'];
} else {
$email['recommended'] = false;
$email['view_category'] = 'available';
$email['trigger_description'] = '';
$email['registry_slug'] = '';
$email['recipient'] = 'reader';
}
$newspack_emails[] = $email;
Comment on lines +339 to +345
usort(
$newspack_emails,
function ( $a, $b ) use ( $category_order ) {
$order_a = $category_order[ $a['category'] ?? '' ] ?? 2;
$order_b = $category_order[ $b['category'] ?? '' ] ?? 2;
return $order_a - $order_b;
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e16b00a. The previous comparator returned 0 for same-category items, and usort is not stable, so within-category order wasn't actually preserved despite the comment. Added a slug-to-index map from array_flip( array_keys( $registry ) ) and used it as a tiebreaker. Unregistered emails fall back to PHP_INT_MAX so they sort to the end. No API schema change — the order map is captured by the closure.

Address Copilot review comment on PR #4727. The previous usort
comparator returned 0 for items in the same category, but PHP's
usort is not stable, so within-category ordering was undefined
despite the comment claiming registry order was preserved.

Add a slug-to-index map from the registry and use it as a
secondary sort key. Unregistered emails sort to the end via
PHP_INT_MAX fallback.

No API schema change — sort is internal to the comparator.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Needs Review The issue or pull request needs to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants