Skip to content

Teach the common changes as recipes and fill in a plugin's own names - #18

Merged
ahegyes merged 15 commits into
trunkfrom
docs/recipes
Oct 28, 2026
Merged

ahegyes merged 15 commits into
trunkfrom
docs/recipes

Conversation

@ahegyes

@ahegyes ahegyes commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

The fill-in swept names no file held since the v2 rebuild and never swept the dws_tpl prefix, so two plugins made from the template declared the same functions and shared every option; it also changed workflows, which GitHub refuses to push with the job's token, and it had never run on the v2 anatomy. Nothing described the plugin as built, and each plugin re-derived the same changes from the framework's sources. The fill-in now gives a plugin its own names, prefix and ports and is rehearsed on every pull request, and the docs describe the anatomy and teach twelve common changes.

  • Fill-in: .github/scripts/fill-in-scaffold.sh validates the inputs (a whole dws_ prefix of one segment that the framework's admin notices accept, and a port pair apart from the template's), renames the entry file, replaces the template's names, prefix and ports outside .github/workflows/, resets the version to 0.1.0, makes README.scaffold.md the README, deletes the disabled example include and the template's recipe test, refreshes the lock's content hash, and fails on anything left. The workflows read the slug from composer.json, and Quality's scaffold job runs the script with sample inputs, then installs, lints and unit-tests the swept plugin.
  • Recipes: docs/recipes.md covers a safeguard with its failure test, a one-time notice, a WordPress settings page, a product data tab, an edit-screen form, a section on WooCommerce's Payments tab, recurring jobs, rewrite rules, a block's view script module, renaming a public hook, wordpress.org distribution, and a build without WooCommerce. The safeguard helper takes its filter's value, so each safeguard keeps a literal, documented hook, and Integration runs SG1 against the recipe's own block.
  • Docs: README.md describes starting a plugin, the fill-in's inputs and the naming; README.scaffold.md lists the demo's parts, what removing each takes, and the settings that follow whatever remains; docs/ARCHITECTURE.md the loading, the boot, where code lives and the data; tests/README.md the suites and CI; AGENTS.md the layout, commands and conventions; SECURITY.md the scope.
  • Pins: every wordpress-configs workflow at Check the built zip's active version against the release wordpress-configs#55's head, whose release checks the built zip's version, and setup-php 2.40.0.

Code +0 −0, tests +66 −0, CI +207 −138, docs +1564 −134.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Build / CI / tooling
  • Documentation
  • Dependency update

Checklist

  • Changelog fragment added (composer changelog:add): none; the v2 rebuild ships as "Initial release."
  • Tests added/updated
  • composer quality-check passes locally
  • Plugin header updated if runtime requirements changed: no runtime requirement changed

Breaking changes for forks

The fill-in's inputs change: prefix is the whole prefix, such as dws_aowc, and wp-env-port is required, with the tests environment on the next port. The fill-in no longer edits .github/workflows/; the workflows read the slug from composer.json's extra.text-domain. The workflows pin ahegyes/wordpress-configs#55 at its head, so that PR merges first; then one commit here moves the workflow pins, package.json's configs dependency and both locks to its merge commit.

Review points.

  • The sweep is a script rather than a called workflow, because a called workflow cannot raise its caller's read-only token for the fill-in's push.
  • The recipes' code was run, not only written: lint, PHPStan and the PHP 7.4 parse on every block, the screens, schedule and rewrite rebuild on a running site, and a full gate, E2E included, on a copy built without WooCommerce.
  • The script-module recipe uses a block's viewScriptModule, because wp-scripts builds script modules only from block.json.
  • README.scaffold.md's removal rows name only their own files; the build gate, the lint scripts and build follow four rules after the table, because a plugin removes the demo's parts in any order.
  • There is no Freemius recipe.

Testing.

  • composer quality-check passes: validate, changelog validation, PHPCS on both profiles, PHPStan level 10, Unit 6 and Integration 70, with the safeguard recipe's 6 rows; two hand mutants of the recipe's helper fail them.
  • npm run test:e2e passes (3 of 3); lint:scripts, lint:styles and lint:types are clean; both audits, actionlint, zizmor and shellcheck are clean.
  • The fill-in, run on a clone with sample inputs, leaves no template name or port, and the swept plugin installs, lints and passes Unit; invalid prefixes and the template's ports are refused.
  • The block, editor-script, models/ and templates/ removal rows applied to a clone, alone and with nothing left to build, leave a plugin that installs, lints, builds and passes Unit.

A safeguard holds even when the build fails, and a plugin relaxes one
only through the safeguard's own filter, with an explicit false, once
the plugin has finished booting. The recipe that teaches it is the
only copy a plugin starts from, so a wrong helper there spreads to
every plugin that has a safeguard.

The Integration suite now loads the recipe's includes/safeguards.php
block from docs/recipes.md and runs its export lock through
map_meta_cap(): no filter, null, 0 and '' keep it locked, false
unlocks it, and false before dws_tpl_initialized keeps it locked. The
recipe does not exist yet, so the rows fail.

Assisted-by: Claude Code:claude-opus-5-5
The fill-in could not produce a working plugin. It swept names no file
has held since the v2 anatomy, never the dws_tpl and DWS_TPL prefix,
so two plugins from the template both declared dws_tpl_plugin() and
shared every dws_tpl_* option; it swept one of the two wp-env ports,
named .wp-env.belowfloor.json and readme.txt, which are gone, and its
check of the first failed the run. It also rewrote three workflows,
and GitHub refuses a GITHUB_TOKEN push that changes a workflow, so its
push would have failed in every plugin.

The sweep is now .github/scripts/fill-in-scaffold.sh, with a required
dws_ prefix and port, the tests environment on the next port, the
scaffold README in place of the template's, the disabled example
include deleted and the lock's content hash refreshed. It never
touches .github/workflows/: Quality, Release and Tests read the slug
from composer.json's text domain. The script fails when any template
name or port is left or a workflow changed. Quality runs it on every
pull request in the template with sample inputs, then installs, lints
and unit-tests the filled-in plugin.

Assisted-by: Claude Code:claude-opus-5-5
Each plugin built from the template re-derived the same changes from
the framework's sources: a safeguard, a one-time notice, a settings
page, a product tab, an edit-screen form, a WooCommerce section,
recurring jobs, rewrite rules, a script module, a hook rename,
wordpress.org distribution and a build without WooCommerce. A wrong
copy of one, such as a relaxation that a half-loaded build can
trigger, spreads to every plugin that has it.

docs/recipes.md now holds them as the files to add or change. The
safeguard recipe carries dws_tpl_is_relaxed(), which takes the filter's
value so each safeguard keeps its literal, documented hook, and its
failure test, which the Integration suite runs against the recipe's
own block. Each recipe's code was probed against the framework: lint,
static analysis and the PHP 7.4 parse, the screens and the schedule on
a running site, and a full gate on a copy built without WooCommerce.

Assisted-by: Claude Code:claude-opus-5-5
The README, AGENTS.md and SECURITY.md still described the v1 template,
with its singleton plugin, installer, config/ files, dependencies/
directory and placeholder table, and nothing described the boot, the
data or the suites.

The README describes starting a plugin from the template and the
fill-in's inputs and naming; docs/ARCHITECTURE.md the loading, the
boot, where code lives and the data; tests/README.md the suites,
environments and CI; AGENTS.md the layout, commands, the plugin's
conventions, including variant children, request context, add-ons and
request reads, and its style; SECURITY.md the scope.

Assisted-by: Claude Code:claude-opus-5-5
The release's built-zip test compared two empty strings, because
wp-env's run consumed the -- before bash -c's arguments, so it passed
for any version; wordpress-configs' fix/release-version-check branch
fixes it and moves its workflows to setup-php 2.40.0. The template's
own jobs still pinned setup-php 2.37.2, which Dependabot proposed
bumping in one of them only.

Every wordpress-configs workflow pin moves to that branch's head,
3f49ee7, so this pull request's release dry run checks the fix, and
the template's three setup-php pins move to 2.40.0. package.json's
configs dependency and both locks follow once the branch is on
GitHub.

Assisted-by: Claude Code:claude-opus-5-5
The sweep accepted a prefix the framework's admin notices refuse, such
as dws_network or one over 32 characters, so the filled-in plugin
threw at composition. A port of 8812 became the tests port's 8813
through the second substitution, and both environments shared it. The
plugin also kept the template's recipe test, which redeclares the
recipe's safeguard helper once a plugin applies the recipe.

The sweep refuses those prefixes and any port that puts either
environment on 8811 or 8812, replaces both ports in one pass, and
deletes the recipe test.

Assisted-by: Claude Code:claude-opus-5-5
The docs said the footprint covered everything under the plugin's
prefix, hidden meta included, but meta on posts, terms, comments and
orders goes only where a meta_prefixes entry names its type. The notice
recipe sent a site notice to every administrator with a scope and a
capability alone, where the first viewing consumes it for everyone.

The docs name what the prefix covers and the meta_prefixes entry, and
the notice recipe adds per_viewer.

Assisted-by: Claude Code:claude-opus-5-5
Following the scaffold README's removal steps broke the plugin: the
greeting's steps left Feature in Plugin::COMPONENTS, whose missing
class the kernel throws on, and its section in the settings tab, and
the block's steps left build and start calling the deleted scripts and
Blocks registering blocks from the deleted manifest on every request.

Each row now names those entries too, and PluginBootTest's expectation
that the greeting is skipped.

Assisted-by: Claude Code:claude-opus-5-5
The block-removal row left lint:scripts naming blocks/src, so ESLint
failed on the missing path and Quality went red for a plugin that
followed it; lint:styles failed the same way once the greeting's SCSS
was gone too.

The row names blocks/src in both scripts.

Assisted-by: Claude Code:claude-opus-5-5
The editor-script row left assets/src in lint:scripts, where ESLint
fails once the folder holds no script, the editor hook in Blocks, and
Blocks in PluginBootTest's resolved components once Blocks goes; the
models/ row left models/ in composer.json's classmap, so Composer
failed to dump the autoloader.

Each row names those entries. The block, editor-script, models/ and
templates/ rows applied together leave a plugin that installs, lints,
builds and passes Unit.

Assisted-by: Claude Code:claude-opus-5-5
The product-tab and edit-screen recipes described a field with its own
render callable without its validate callable, and the framework
refuses to save a custom control's value that nothing validates, so
such a field rejected every submission and kept its old value.

Both recipes say the field takes a validate callable that returns the
value to store or a WP_Error.

Assisted-by: Claude Code:claude-opus-5-5
The setup steps never installed a browser, and neither npm run setup
nor the Playwright configuration downloads one, so on a fresh machine
npm run test:e2e failed for a missing Chromium.

README.md, README.scaffold.md and tests/README.md install Chromium,
the browser the configuration runs, once before the suite.

Assisted-by: Claude Code:claude-opus-5-5
The block-removal row's example points the entry's build gate at
assets/build/editor.asset.php, and the editor-script row then deleted
the entry that writes it, so the gate found no build and the plugin
never booted.

The editor-script row points the gate at a file the build still
writes, and drops the gate's built-file check once nothing is built.

Assisted-by: Claude Code:claude-opus-5-5
Each removal row named the build gate and lint paths its own removal
touched, so following two rows in turn broke what the first had set:
the editor row repointed the gate at the greeting's build, which the
greeting row deletes, and removing every stylesheet left lint:styles
matching nothing, which Stylelint refuses.

The rows name only their own files, and four rules after the table
cover any combination: the gate follows the remaining build, the lint
scripts name only folders that still hold files, lint:styles keeps a
pattern with --allow-empty-input once no SCSS is left, and build stays
for its callers. A plugin left with nothing to build installs, lints
and passes Unit under them.

Assisted-by: Claude Code:claude-opus-5-5
The workflows pinned the version-check fix's branch head, 3f49ee7, so
this pull request's release dry run would check the fix before it
merged, while the npm tooling and the Composer lock stayed on a6a7073.

All of them pin 9f64dc7, configs' trunk, which merges that branch.
Since a6a7073, configs changed only its workflows and their tests, so
the npm package and the Composer package behave as before.

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes
ahegyes merged commit cc63f93 into trunk Oct 28, 2026
29 checks passed
@ahegyes
ahegyes deleted the docs/recipes branch October 28, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant