Repository navigation
Teach the common changes as recipes and fill in a plugin's own names - #18
Merged
Merged
Conversation
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
This was referenced Oct 28, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The fill-in swept names no file held since the v2 rebuild and never swept the
dws_tplprefix, 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..github/scripts/fill-in-scaffold.shvalidates the inputs (a wholedws_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, makesREADME.scaffold.mdthe 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 fromcomposer.json, and Quality'sscaffoldjob runs the script with sample inputs, then installs, lints and unit-tests the swept plugin.docs/recipes.mdcovers 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.README.scaffold.mdlists the demo's parts, what removing each takes, and the settings that follow whatever remains;docs/ARCHITECTURE.mdthe loading, the boot, where code lives and the data;tests/README.mdthe suites and CI; AGENTS.md the layout, commands and conventions; SECURITY.md the scope.Code +0 −0, tests +66 −0, CI +207 −138, docs +1564 −134.
Type of change
Checklist
composer changelog:add): none; the v2 rebuild ships as "Initial release."composer quality-checkpasses locallyBreaking changes for forks
The fill-in's inputs change:
prefixis the whole prefix, such asdws_aowc, andwp-env-portis required, with the tests environment on the next port. The fill-in no longer edits.github/workflows/; the workflows read the slug fromcomposer.json'sextra.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.
viewScriptModule, because wp-scripts builds script modules only fromblock.json.README.scaffold.md's removal rows name only their own files; the build gate, the lint scripts andbuildfollow four rules after the table, because a plugin removes the demo's parts in any order.Testing.
composer quality-checkpasses: 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:e2epasses (3 of 3);lint:scripts,lint:stylesandlint:typesare clean; both audits, actionlint, zizmor and shellcheck are clean.models/andtemplates/removal rows applied to a clone, alone and with nothing left to build, leave a plugin that installs, lints, builds and passes Unit.