Skip to content

Commit 678b222

Browse files
authored
Merge pull request #4499 from Automattic/trunk
Alpha release Feb 19
2 parents b475f93 + aaf47b2 commit 678b222

136 files changed

Lines changed: 8863 additions & 3591 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# Newspack Plugin: Agent Instructions
2+
3+
This file covers what is specific to `newspack-plugin`. Shared conventions (Docker commands, `n` script, coding standards, git rules, etc.) are in the root `newspack-workspace/AGENTS.md`.
4+
5+
## Linting Commands
6+
7+
```bash
8+
npm run lint # JS + SCSS only (see gotchas)
9+
npm run lint:js # JavaScript/TypeScript linting
10+
npm run lint:scss # SCSS linting
11+
npm run lint:php # PHP linting (PHPCS)
12+
npm run fix:js # Auto-fix JS issues
13+
npm run fix:php # Auto-fix PHP issues (PHPCBF)
14+
```
15+
16+
## Common Gotchas
17+
18+
- `npm run lint` runs JS + SCSS only. PHP linting requires a separate `npm run lint:php`.
19+
- After adding a new PHP file, run `composer dump-autoload` to update the classmap (Composer uses `classmap`, not PSR-4).
20+
- Individual JS test files cannot run independently. Always run `npm test` for the full suite.
21+
- Never import `react-router-dom` directly in source code. Use the proxy: `import Router from '../../packages/components/src/proxied-imports/router'`. Tests may import `react-router-dom` directly.
22+
- New standalone webpack entry points must import `src/shared/js/public-path.js` first.
23+
- Plugin integration classes in `includes/plugins/` use the root `Newspack` namespace despite living in subdirectories.
24+
25+
## PHP Backend
26+
27+
### Bootstrap & Autoloading
28+
29+
- **`newspack.php`**: Main plugin file, defines constants (`NEWSPACK_ABSPATH`, `NEWSPACK_PLUGIN_FILE`, etc.), requires Composer autoloader and Action Scheduler.
30+
- **`includes/class-newspack.php`**: Singleton main class. Manually `include_once`s files in a specific order via `includes()`, then hooks `init()` methods.
31+
- **Autoloading**: Composer `classmap` strategy (not PSR-4). After adding a new PHP file, run `composer dump-autoload` to update the classmap.
32+
33+
### Class Initialization Patterns
34+
35+
New classes should follow the **static `init()` pattern** (dominant, used by 100+ classes):
36+
37+
```php
38+
namespace Newspack;
39+
40+
class My_Feature {
41+
public static function init() {
42+
add_action( 'init', [ __CLASS__, 'register_things' ] );
43+
}
44+
// ... static methods ...
45+
}
46+
My_Feature::init();
47+
```
48+
49+
Two other patterns exist but are legacy or special-purpose:
50+
- `new ClassName()` at file bottom (rare, ~7 classes like `API`, `Profile`)
51+
- `Newspack::instance()` singleton (only the main class)
52+
53+
Classes that should not be extended are marked `final`.
54+
55+
### Namespace Map
56+
57+
| Namespace | Directory |
58+
|-----------|-----------|
59+
| `Newspack` | `includes/` (root, most classes) |
60+
| `Newspack\API` | `includes/api/` |
61+
| `Newspack\CLI` | `includes/cli/` |
62+
| `Newspack\Data_Events` | `includes/data-events/` |
63+
| `Newspack\Data_Events\Connectors` | `includes/data-events/connectors/` |
64+
| `Newspack\Reader_Activation` | `includes/reader-activation/` |
65+
| `Newspack\Reader_Activation\Sync` | `includes/reader-activation/sync/` |
66+
| `Newspack\Reader_Activation\Integrations` | `includes/reader-activation/integrations/` |
67+
| `Newspack\Wizards` | `includes/wizards/` |
68+
| `Newspack\Wizards\Newspack` | `includes/wizards/newspack/` |
69+
| `Newspack\Wizards\Traits` | `includes/wizards/traits/` |
70+
| `Newspack\Optional_Modules` | `includes/optional-modules/` |
71+
| `Newspack\Content_Gate` | `includes/content-gate/` |
72+
| `Newspack\Collections` | `includes/collections/` |
73+
74+
### REST API
75+
76+
Two namespace constants, defined in `includes/util.php`:
77+
- `NEWSPACK_API_NAMESPACE` = `newspack/v1` (primary)
78+
- `NEWSPACK_API_NAMESPACE_V2` = `newspack/v2`
79+
80+
Three patterns for registering routes (in order of prevalence):
81+
82+
1. **Wizard Section routes** (most common): Extend `Wizard_Section`, implement `register_rest_routes()` (auto-hooked to `rest_api_init`). Route pattern: `wizard/{wizard_slug}/{section}`. Permission: `$this->api_permissions_check()`.
83+
84+
2. **Wizard routes**: Extend `Wizard`, implement `register_api_endpoints()`. Route pattern: `wizard/{slug}/...`. Permission: `$this->api_permissions_check()`.
85+
86+
3. **Standalone controllers**: In `includes/api/`, extend `WP_REST_Controller`. Only 2 exist (`Plugins_Controller`, `Wizards_Controller`).
87+
88+
Common sanitize callbacks from `util.php`: `Newspack\newspack_clean()`, `Newspack\newspack_string_to_bool()`.
89+
90+
### Wizard System (PHP side)
91+
92+
Two levels of abstraction:
93+
94+
- **`Wizard`** (abstract, `includes/wizards/class-wizard.php`): Override `$slug`, `$capability`, `get_name()`. Renders an empty `<div>` for React hydration. Provides `api_permissions_check()`, completion tracking, admin menu registration.
95+
96+
- **`Wizard_Section`** (abstract, `includes/wizards/class-wizard-section.php`): Modular sections within a wizard. Set `$wizard_slug`, implement `register_rest_routes()`. Used by 9+ section classes under `includes/wizards/newspack/` (Emails, Pixels, Social, etc.).
97+
98+
For the frontend counterpart, see **Frontend > Wizard System** below.
99+
100+
### Plugin Integrations
101+
102+
Simple integrations live in single files: `includes/plugins/class-{plugin}.php`. Complex integrations span subdirectories: `includes/plugins/woocommerce/`, `includes/plugins/co-authors-plus/`, `includes/plugins/woocommerce-subscriptions/`, etc.
103+
104+
Some integrations have corresponding Configuration Managers in `includes/configuration_managers/`.
105+
106+
### Optional Modules
107+
108+
Feature flags managed by `includes/optional-modules/class-optional-modules.php`. Check the class for the current module list. Enable/disable via `wp newspack optional-modules enable|disable|list`.
109+
110+
### Settings & Data Storage
111+
112+
| Mechanism | Convention | Examples |
113+
|-----------|-----------|----------|
114+
| `wp_options` | Prefixed per subsystem | `newspack_reader_activation_*`, `newspack_donation_*` |
115+
| Custom Post Types | Short prefix | `newspack_rr_email`, `np_content_gate` |
116+
| User meta | `np_` prefix | `np_reader`, `np_reader_email_verified` |
117+
| Feature flag constants | In `wp-config.php` | `NEWSPACK_CONTENT_GATES`, `NEWSPACK_LOG_LEVEL` |
118+
119+
### Logging
120+
121+
`Newspack\Logger` provides:
122+
- `Logger::log( $payload, $header, $type )`: gated by `NEWSPACK_LOG_LEVEL` constant (0 = off, 1 = basic, 2 = verbose).
123+
- `Logger::newspack_log( $code, $message, $data, $type )`: fires `newspack_log` action (consumed by Newspack Manager).
124+
125+
Subsystems use a `LOGGER_HEADER` constant (e.g., `Data_Events::LOGGER_HEADER = 'NEWSPACK-DATA-EVENTS'`).
126+
127+
### WP-CLI Commands
128+
129+
All under the `newspack` namespace, defined in `includes/cli/`. Run `wp newspack --help` to list available subcommands.
130+
131+
### PHP Testing
132+
133+
```bash
134+
npm run lint:php # PHP linting (PHPCS)
135+
npm run fix:php # Auto-fix PHP issues (PHPCBF)
136+
```
137+
138+
- Tests live in `tests/unit-tests/`, extend `WP_UnitTestCase`.
139+
- Bootstrap: `tests/class-newspack-unit-tests-bootstrap.php`. Mocks in `tests/mocks/`.
140+
- Available `@group` annotations: `byline-block`, `corrections`, `Access_Rules`, `WooCommerce_Subscriptions_Integration`.
141+
- Run tests via `n test-php` from the repo directory (see parent AGENTS.md for flags).
142+
143+
## Frontend (JS/React/TypeScript)
144+
145+
### Wizard System (two coexisting architectures)
146+
147+
For the PHP backend counterpart, see **PHP Backend > Wizard System** above.
148+
149+
**Modern pattern (use for new code):**
150+
- Single entry point: `src/wizards/index.tsx` uses `React.lazy()` + `Suspense` to load views by `?page=` URL param.
151+
- Views are function components (`.tsx` preferred).
152+
- Data fetching: `useWizardApiFetch(slug)` hook from `src/wizards/hooks/`.
153+
- Composition helpers: `WizardsTab`, `WizardSection`, `WizardsActionCard` from `src/wizards/`.
154+
- Used by: Dashboard, Settings, Audience pages.
155+
156+
**Legacy pattern (found in older wizards):**
157+
- Standalone webpack entry per wizard: `src/wizards/{name}/index.js`.
158+
- Uses `withWizard(Component, requiredPlugins)` HOC.
159+
- Data fetching via `wizardApiFetch` prop (from HOC).
160+
- Mounts via `createRoot()` + `render()`.
161+
- Used by: Setup, Newsletters, Advertising.
162+
163+
### State Management
164+
165+
`@wordpress/data` custom store, namespace `newspack/wizards` (`WIZARD_STORE_NAMESPACE`):
166+
- Key selectors: `getWizardAPIData(slug)`, `getWizardData(slug)`, `isLoading()`.
167+
- Key actions: `wizardApiFetch`, `saveWizardSettings`.
168+
- Auto-fetches data from `/newspack/v1/wizard/{slug}` via resolver.
169+
- Helper: `useWizardData(wizardName)` from `packages/components/src/wizard/store/utils.js`.
170+
171+
Reader activation frontend uses a separate localStorage-based store (`src/reader-activation/store.js`), not `@wordpress/data`.
172+
173+
### TypeScript Conventions
174+
175+
Mixed JS/TS codebase (~30% TypeScript). Newer wizard views are `.tsx`; older wizards, blocks, and most components remain `.js`.
176+
177+
- Config extends `newspack-scripts/config/tsconfig.json` (strict mode).
178+
- Type declarations: ambient `.d.ts` files with global types (no imports needed). Located at `src/wizards/types/` and feature-level `types.d.ts`.
179+
- Window globals typed via `declare global { interface Window { ... } }`.
180+
181+
### Import Conventions
182+
183+
**Order** (each group separated by a blank line with a JSDoc comment header):
184+
1. `/** External dependencies */` (classnames, lodash, etc.)
185+
2. `/** WordPress dependencies */` (@wordpress/\*)
186+
3. `/** Internal dependencies */` (relative paths)
187+
188+
**Component library**: Import from `packages/components/src` via relative paths (no webpack alias):
189+
```js
190+
import { Button, ActionCard } from '../../packages/components/src';
191+
```
192+
193+
**Router**: In source code, always import through the proxy, never directly from `react-router-dom` (tests may import directly):
194+
```js
195+
import Router from '../../packages/components/src/proxied-imports/router';
196+
const { HashRouter, Route, Switch } = Router;
197+
```
198+
199+
**Colors in JS**: `import colors from '../../packages/colors/colors.module.scss';`
200+
201+
### Webpack Entry Points
202+
203+
Base config from `newspack-scripts`, which extends `@wordpress/scripts`.
204+
205+
**Auto-discovered entries:**
206+
- Wizards: `src/wizards/*/index.{js,tsx}` (~7 standalone entries)
207+
- Other scripts: `src/other-scripts/*/index.js` (~11 entries)
208+
209+
**Hardcoded entries (~30)**: reader-activation scripts, content-gate scripts, my-account variants, admin/editor scripts, blocks, collections, newspack-ui, bylines, and more. All declared in `webpack.config.js`.
210+
211+
**Code splitting**: Hashed chunk filenames, commons split chunk. Public path set dynamically via `src/shared/js/public-path.js` from `window.newspack_urls.public_path`. Any standalone entry point must import this file first.
212+
213+
**Ad blocker workaround**: `advertising/` wizard bundled as `billboard.js`.
214+
215+
### Gutenberg Blocks
216+
217+
Blocks in `src/blocks/` with `block.json` metadata. Central registration in `src/blocks/index.js`.
218+
219+
- Each block exports `{ metadata, name, settings }`.
220+
- Conditional registration based on `window.newspack_blocks` feature flags (`has_reader_activation`, `corrections_enabled`, `collections_enabled`, `has_memberships`, `is_block_theme`).
221+
- Icons from `packages/icons/` with foreground color from `packages/colors/`. See `packages/icons/DEVELOPMENT.md` for the icon selection hierarchy (prefer `@wordpress/icons` first, then Newspack icons) and React/PHP usage patterns.
222+
- Some blocks have separate webpack entries for frontend `view.js` scripts.
223+
224+
### SCSS & Color System
225+
226+
- Design tokens in `packages/colors/colors.module.scss` (primary, secondary, tertiary, quaternary, neutral + semantic colors, each with 000-1000 scale).
227+
- See `packages/colors/DEVELOPMENT.md` for the color usage decision tree: backend admin uses WordPress colors (with `primary-600` accent override), block icons must use `primary-400`, frontend Newspack UI uses `newspack-colors`, theme elements use the theme palette.
228+
- BEM-ish naming with `newspack-` prefix (e.g., `.newspack-wizard__header`, `.newspack-card`).
229+
- Tachyons CSS utility library available for utility classes.
230+
- Shared mixins in `src/shared/scss/_mixins.scss`.
231+
232+
### JS Testing
233+
234+
```bash
235+
npm test # IMPORTANT: Always run the full suite. Individual test files cannot run independently.
236+
npm run tsc # TypeScript type checking (watch mode, no emit)
237+
```
238+
239+
- Jest via `newspack-scripts test` (wraps `@wordpress/scripts`).
240+
- Test files colocated with source using `.test.js` suffix.
241+
- Libraries: `@testing-library/react` (`render`, `fireEvent`, `waitFor`, `screen`).
242+
- Mocking: `jest.mock()` for `@wordpress/data`, `@wordpress/api-fetch`; direct manipulation of `window.*` globals.
243+
244+
## Recipes
245+
246+
### Add a new wizard section
247+
248+
1. Create a PHP class in `includes/wizards/newspack/` extending `Wizard_Section`.
249+
2. Set `$wizard_slug` to match the parent wizard, implement `register_rest_routes()`.
250+
3. `include_once` the file in `includes/class-newspack.php` (order matters).
251+
4. Run `composer dump-autoload`.
252+
5. Create a React component in `src/wizards/` (use modern pattern: function component, `.tsx`, `useWizardApiFetch`).
253+
6. Add a `React.lazy()` import in `src/wizards/index.tsx` mapped to the `?page=` param.
254+
255+
### Add a new block
256+
257+
1. Create a directory in `src/blocks/<block-name>/` with `block.json`, `edit.js`, `index.js`.
258+
2. Export `{ metadata, name, settings }` from `index.js`.
259+
3. Register the block in `src/blocks/index.js` (add feature flag condition if needed via `window.newspack_blocks`).
260+
4. If the block needs a PHP render callback, register it in `includes/class-blocks.php`.
261+
5. If the block needs a frontend script, add a `view.js` and a hardcoded webpack entry in `webpack.config.js`.
262+
263+
### Add a new plugin integration
264+
265+
1. Create `includes/plugins/class-{plugin-name}.php` using the root `Newspack` namespace.
266+
2. Follow the static `init()` pattern (see Class Initialization Patterns above).
267+
3. `include_once` the file in `includes/class-newspack.php`.
268+
4. Run `composer dump-autoload`.
269+
5. Optionally add a Configuration Manager in `includes/configuration_managers/` for setup UI.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

0 commit comments

Comments
 (0)