feat: add wp-api/speculation scaffold - #56
Conversation
First scaffold in the new wp-api category and the first to use the reserved "wizard_step": "wp-apis". Generates a Registrable that pins. Generates a Registrable that pins speculative-loading MODE/EAGERNESS via wp_speculation_rules_configuration and merges EXCLUDE_PATHS into wp_speculation_rules_href_exclude_paths, no-op guarded on WP < 6.8; wires into the wp/registrable module/anchor.
There was a problem hiding this comment.
Pull request overview
Adds the first wp-api scaffold for configuring WordPress Speculation Rules.
Changes:
- Adds the speculation scaffold and PHPUnit template.
- Integrates discovery, PSR-4 layout, and Registrable wiring.
- Updates tests, skills, documentation, and changelog.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
tests/scaffolds/bundled-manifests.test.js |
Tests scaffold discovery and wiring. |
skills/scaffold/SKILL.md |
Documents scaffold usage and testing. |
scaffolds/wp-api/speculation/templates/test.php.mustache |
Generates PHPUnit coverage. |
scaffolds/wp-api/speculation/templates/speculation.php.mustache |
Generates the Speculation Rules service. |
scaffolds/wp-api/speculation/scaffold.json |
Defines inputs, outputs, and wiring. |
scaffolds/setup/claude-skills/templates/scaffold-SKILL.md |
Updates the generated skill documentation. |
docs/authoring-scaffolds.md |
Documents the wp-api category. |
CHANGELOG.md |
Records the new scaffold. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
node-packages/wp-tooling/scaffolds/wp-api/speculation/scaffold.json:46
- The
modeandeagernessinputs accept arbitrary strings, even though both are closed WordPress enums.resolveInputs()performs no value validation, so a typo such as--mode=prerenderinggenerates a constant that core silently replaces with its default and a generated PHPUnit test that is guaranteed to fail; quotes can also make the generated PHP invalid. Add declarative allowed-values support and reject invalid supplied values before rendering.
"key": "mode",
"description": "Speculative loading mode written into the MODE constant. One of 'prefetch' (fetch the document early) or 'prerender' (render it in a hidden tab). 'auto' hands the choice back to core, which resolves it to 'prefetch'.",
"default": "prerender"
node-packages/wp-tooling/tests/scaffolds/bundled-manifests.test.js:108
- This does not actually render either new Mustache template:
ScaffoldRegistry.execute()skips reading and rendering file/test bodies whendryRunis true (registry.js:378-380and419-421). Consequently malformed PHP or an unresolved placeholder in either new template would not fail this integration test despite its stated render coverage. Execute non-dry-run against the temporary directory so template rendering is exercised.
{ dryRun: true, cwd: target }
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (6)
node-packages/wp-tooling/scaffolds/wp-api/speculation/scaffold.json:51
eagernesslikewise accepts any supplied string, while WordPress only supportsauto,conservative,moderate, oreager. Invalid input therefore generates a scaffold that does not apply the requested setting and whose generated PHPUnit test fails. Add and enforce a closed set of allowed values.
"key": "eagerness",
"description": "How keenly the browser acts on the rules, written into the EAGERNESS constant. One of 'conservative' (on pointer/touch down), 'moderate' (on hover, this scaffold's default) or 'eager' (as soon as the link is known). 'auto' hands the choice back to core, which resolves it to 'conservative'.",
"default": "moderate"
node-packages/wp-tooling/src/scaffolds/registry.js:885
- Valid Composer PSR-4 fallback mappings can use an empty prefix (
"": "src/"). For that map, the precedingif (!firstKey)returns before this path branch, sobase_pathincorrectly keepsincludes/...instead of followingsrc/.... Distinguishundefinedfrom an empty key for path inputs, preserve the namespace fallback behavior, and cover this mapping in the discovery tests.
if (isPathInput(decl.key)) {
const def =
typeof decl.default === 'string' ? decl.default : '';
return graftPath(map[firstKey], def);
node-packages/wp-tooling/scaffolds/wp-api/speculation/scaffold.json:46
modeis unrestricted even though it is emitted directly into a PHP constant and WordPress accepts onlyauto,prefetch, orprerender. A typo currently scaffolds successfully but core silently ignores the requested mode; a quote can also produce invalid PHP. Add and enforce a closed set of allowed input values before rendering.
This issue also appears on line 49 of the same file.
"key": "mode",
"description": "Speculative loading mode written into the MODE constant. One of 'prefetch' (fetch the document early) or 'prerender' (render it in a hidden tab). 'auto' hands the choice back to core, which resolves it to 'prefetch'.",
"default": "prerender"
node-packages/wp-tooling/skills/scaffold/SKILL.md:79
- This path is not always
includes/Services/anymore: the new PSR-4 directory discovery intentionally rendersinc/Services/orsrc/Services/for those project roots. Update the skill table to describe the discovered root directory (and the other affected rows), otherwise the scaffold guidance contradicts the engine and the new integration test.
| `wp-api/speculation` | `includes/Services/` | `<Root>\Services` | `tests/Services/` | `<Root>\Tests\Services` | `<Root>\Modules\Services` |
node-packages/wp-tooling/scaffolds/setup/claude-skills/templates/scaffold-SKILL.md:79
- This generated skill copy also hard-codes
includes/Services/, although the changed resolver writes under the consuming project's PSR-4 directory (inc/Services/,src/Services/, etc.). Introduce a root-directory placeholder and update the affected table rows so generated guidance matches actual scaffold output.
| `wp-api/speculation` | `includes/Services/` | `<Root>\Services` | `tests/Services/` | `<Root>\Tests\Services` | `<Root>\Modules\Services` |
node-packages/wp-tooling/docs/authoring-scaffolds.md:267
- Adding the new top-level
wp-api/category leaves the package directory guide stale:node-packages/wp-tooling/AGENTS.md:42still states thatscaffolds/contains onlyci/,lint/,setup/, andwp/. Update that contributor-facing layout alongside this category documentation.
`wp` holds the framework-shaped kinds (a CPT, a REST controller, a CLI command). `wp-api` holds scaffolds that customise a **modern WordPress core API** — code whose shape is dictated by core's own hooks and which must be guarded against the WordPress version that introduced them (`wp-api/speculation`, Speculation Rules, WP 6.8). Those pair with `"wizard_step": "wp-apis"`.
What this PR does
Adds the first
wp-apicategory scaffold (wp-api/speculation) — a template that generates a PSR-4Registrablecustomising WordPress core's Speculation Rules API (core since WP 6.8), plus the first use of the schema's previously reserved"wizard_step": "wp-apis"and lockstep docs/SKILL/test updates so the new kind is discovered, validated, and wired end-to-end.Part of
Part of rtcamp/wp-devtools#61
Changes
scaffolds/wp-api/speculation/(scaffold.json+speculation.php.mustache+test.php.mustache).mode/eagerness(inputs; defaultsprerender/moderate) viawp_speculation_rules_configuration, returning non-array configs unchanged so speculative loading another filter disabled is never revived.EXCLUDE_PATHSconst (const-only, docblock examples for/cart/-style paths) intowp_speculation_rules_href_exclude_paths, preserving other callers' paths (array_unique+array_values).register_hooks()no-ops on WP < 6.8 (function_exists( 'wp_get_speculation_rules' )guard).wp/registrable({{base_path}}/../Modules/Services.php, anchor// scaffold:wp/registrable:classes); PSR-4 namespace/paths discovered fromcomposer.json.wp-apicategory documented indocs/authoring-scaffolds.md; first scaffold to use the reserved"wizard_step": "wp-apis".wp-api/speculationadded to both copies of the scaffold SKILL (kind table, test checklist, PHPUnit framework row).tests/scaffolds/bundled-manifests.test.jspinning render layout, discovered namespace, and wiring-anchor reuse.CHANGELOG.mdentry under## Unreleased.How I verified
Acceptance criteria
Runtime behavior
Code quality
Housekeeping
Reviewer notes