Skip to content

Name every hook by the slash grammar and move to GPL-3.0-or-later - #66

Merged
ahegyes merged 4 commits into
trunkfrom
feat/hook-grammar-and-gpl3
Oct 30, 2026
Merged

ahegyes merged 4 commits into
trunkfrom
feat/hook-grammar-and-gpl3

Conversation

@ahegyes

@ahegyes ahegyes commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Summary. Two changes ahead of the 2.0.0 tag:

  • every hook the framework fires takes a {prefix}/area/name name whose last level says what kind of hook it is;

  • the framework moves from GPL-2.0-or-later to GPL-3.0-or-later.

  • Hook names: {prefix}_snake gave one delimiter two jobs, the boundary between levels and the boundary between words, and a name did not say whether its hook is an event, a decision or a value.

    • / separates levels and _ separates words.
    • The last level is a past participle for an event, an imperative for a scheduled or command hook, a predicate (is_, has_, can_, should_) for a boolean filter, and the noun of the value for any other filter, plural for a list.
    • WordPress tells actions from filters by no name form, so AGENTS.md Style states the grammar, and convention 5 and limitation 1 name the boot hook.
    Class Before After
    PluginBoot {prefix}_initialized, {prefix}_initialization_failure, {prefix}_initialization_blocked {prefix}/boot/succeeded, {prefix}/boot/failed, {prefix}/boot/blocked
    AdminNotices {prefix}_notice_visible, {prefix}_notice_message, {prefix}_notice_dismissed {prefix}/admin_notice/should_show, {prefix}/admin_notice/message, {prefix}/admin_notice/dismissed
    MigrationRunner {option}_run, with the admin-post action and nonce {option}_run_now {option}/migration/run, with {option}/migration/run_now
    OptionsPage, ObjectForm, ProductDataTab {id}_tabs, {id}_fields, {id}_groups {id}/options_page/tabs, {id}/object_form/fields, {id}/product_data_tab/groups
    Updater dws_github_updater_config_{slug} dws_github_updater/config/{slug}
    • A hook built from an id or option name the consumer passes takes it whole as its first level, because the id also names a nonce, an option or a DOM id; any other dynamic part goes last.
    • admin-post.php reads its action through sanitize_text_field(), which keeps /, so the run-now link still dispatches.
    • consumer-a's own safeguard filter follows the grammar as acme_ca/exports/should_restrict.
  • Migration runner: the scheduled hook {option}/migration/run is longer than the lock {option}_lock through which the constructor validated the option, so the runner builds its ScheduledHook in the constructor. An option too long to name the hook fails at construction instead of when a batched step first schedules its continuation.

  • License: every v1 plugin and framework package, and WooCommerce, is GPL-3.0-or-later. A plugin combining them with a GPL-2.0-or-later framework ships under GPLv3 through the "or later" clause, so its header would not state the license it ships under.

    • The root and package LICENSE files hold the GNU text of version 3.
    • The root, package and npm manifests declare GPL-3.0-or-later, and AGENTS.md names it.
    • The fixture plugins' headers, manifests and locked framework entries follow; third-party packages in the locks keep their own licenses.
  • Packages: the Composer lock moves to WordPress core 7.1.4, PHPStan 2.3.4, php-code-coverage 14.5.0 and Symfony patch releases. The npm lock stays as it is, because npm's seven-day release-age cooldown resolved older @wordpress packages than the lock holds.

Production code +32 −22 (7 files), tests +69 −61 (12 files), fixtures +28 −28 (20 files), docs +5 −4 (2 files), the six LICENSE files, eight manifests +8 −8, and eight locks (+3,944 −1,959 in all, nearly all the license text).

Review points.

  • No alias: nothing is tagged, so the old names get no deprecated bridge, @version stays 2.0.0 and no changelog fragment ships. A development site that queued a migration job under {option}_run keeps it until Action Scheduler fails it, since no callback listens.
  • wordpress-plugin-template: its lock pins a framework commit, so it keeps the old names until its own follow-up renames its safeguard recipe, footprint and docs and moves the lock to this trunk.
  • Stricter than WordPress: core and WooCommerce name hooks with underscores only. The slash shape follows ACF, Elementor, Rank Math, Query Monitor and Timber, and the last-level grammar goes further than any of them, which is why AGENTS.md states it.
  • The visibility filter's first parameter is documented as $show, matching should_show.

Testing.

  • composer quality-check passes: validate, changelog validation, PHPCS on both profiles, the PHPStan profiles and every fixture's, Unit 192 and Integration 1,346.
  • composer test:integration:multisite passes (41), and so does composer test:integration:php74 (2), which loads the bootstrap package on PHP 7.4.
  • Before the change: the renamed Integration tests failed on the old code, 41 of 193, and the runner's new row failed until the constructor built the hook.
  • Review: Claude code review at high and two Codex rounds; the last round found nothing new.

Refresh the Composer lock: WordPress core 7.1.4, PHPStan 2.3.4,
php-code-coverage 14.5.0 and Symfony patch releases; theseer/tokenizer
drops out as no package requires it any more. The npm lock stays as it
is, because npm's seven-day release-age cooldown on this host resolved
older @WordPress packages than the lock already holds. No code change
was needed.

Assisted-by: Claude Code:claude-opus-5-5
The framework's hooks take {prefix}_snake names, which give a reader one
delimiter for two jobs: the boundary between levels and the boundary
between words. The tests now expect the names the owner ruled: every
hook is {prefix}/area/name, and its last level says what kind of hook
it is.

- boot outcomes: {prefix}/boot/succeeded, /boot/failed, /boot/blocked;
- admin notices: {prefix}/admin_notice/should_show, /message,
  /dismissed;
- the migration runner's scheduled hook {option}/migration/run and its
  admin-post action {option}/migration/run_now;
- hooks built from a consumer's id: {id}/options_page/tabs,
  {id}/object_form/fields, {id}/product_data_tab/groups;
- the updater's dws_github_updater/config/{slug}.

consumer-a's own safeguard filter follows the same grammar as
acme_ca/exports/should_restrict. Against the current code, 41 of the
193 affected Integration tests fail.

Assisted-by: Claude Code:claude-opus-5-5
The framework fired {prefix}_snake hooks, whose single delimiter hides
the boundary between levels, and whose names did not say whether a
hook is an event, a decision or a value. Every hook is now
{prefix}/area/name with a last level that names its kind: a past
participle for an event, an imperative for a scheduled hook, a
predicate for a boolean filter and the value's noun otherwise.

- PluginBoot fires {prefix}/boot/succeeded, /boot/failed and
  /boot/blocked;
- AdminNotices filters {prefix}/admin_notice/should_show and /message
  and fires /admin_notice/dismissed;
- MigrationRunner schedules {option}/migration/run, which also names
  its admin-post action and nonce, {option}/migration/run_now;
- OptionsPage, ObjectForm and ProductDataTab filter
  {id}/options_page/tabs, {id}/object_form/fields and
  {id}/product_data_tab/groups;
- the bootstrap updater filters dws_github_updater/config/{slug}.

AGENTS.md states the grammar under Style, because WordPress tells
actions from filters by no name form, and names the boot hook in
convention 5 and limitation 1. Nothing is tagged, so no alias bridges
the old names.

Assisted-by: Claude Code:claude-opus-5-5
Every v1 plugin and framework package, and WooCommerce, the host of
most plugins on the framework, is GPL-3.0-or-later, while the v2
framework carried GPL-2.0-or-later. A plugin combining the two ships
under GPLv3 through the "or later" clause, so its own header would not
state the license it ships under. Moving the framework to
GPL-3.0-or-later gives one license across the stack and needs no
consent for code derived from v1.

The root and package LICENSE files hold the GNU text of version 3, the
root and package manifests and the npm manifest declare
GPL-3.0-or-later, AGENTS.md names it, and the fixture plugins' headers,
manifests and locked framework entries follow. Third-party packages in
the locks keep their own licenses.

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes ahegyes changed the title Feat/hook grammar and gpl3 Name every hook by the slash grammar and move to GPL-3.0-or-later Oct 30, 2026
@ahegyes
ahegyes merged commit 2186c53 into trunk Oct 30, 2026
24 checks passed
@ahegyes
ahegyes deleted the feat/hook-grammar-and-gpl3 branch October 30, 2026 10: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