Skip to content

Review/multiple plugins#14

Merged
troychaplin merged 11 commits intomainfrom
review/multiple-plugins
Apr 18, 2026
Merged

Review/multiple plugins#14
troychaplin merged 11 commits intomainfrom
review/multiple-plugins

Conversation

@troychaplin
Copy link
Copy Markdown
Owner

Pull Request

Description

Wholesale Gutenberg-alignment refactor + post-batch polish pass. Brings the plugin's internal architecture, file layout, naming, and tests in line with current Gutenberg package conventions, preparing the codebase for a future core-merge proposal. Public API (global PHP functions, JS filter names, store name, registration hooks) is preserved — existing consumers work without code changes, with the single exception of the REST endpoint path (coordinated with the only consumer, validation-api-settings, in the same commit).

Represents 11 commits across five alignment batches, a four-item polish pass, and a documentation refresh. See docs/gutenberg-alignment/consolidated-plan.md for the full execution record and docs/PR-READINESS.md for where this work sits relative to a future Gutenberg core PR.

Type of Change

  • 🐛 Bug fix
  • ✨ New accessibility check
  • 🔌 New developer API feature
  • 📚 Documentation update
  • 🔧 Code refactoring
  • ♿ Accessibility improvement
  • 💥 Breaking change

Related Issues

Related issue(s): N/A — internal alignment work. Tracked in docs/gutenberg-alignment/consolidated-plan.md.

Changes Made

Five-batch alignment plan:

  • Batch 1 — JS source reshape to Gutenberg-package layout. Flat src/{store, utils, hooks, components}/. Renderless ValidationProvider/ValidationAPI converted to useValidationSync + useValidationLifecycle hooks (invoked from sibling renderless wrappers to avoid render loops). editor.preSavePost save-time safety gate added. useValidationIssues() consolidated hook extracted. useMetaField dual useSelect collapsed. getInvalid* renamed to useInvalid*. Webpack aliases dropped. package.json sideEffects declared.
  • Batch 2 — REST namespace moved from wp/v2/validation-checks (reserved core namespace) to plugin-owned wp-validation/v1/checks. Final core namespace deferred to PR review. Settings addon updated in lockstep.
  • Batch 3 — Deleted includes/Core/I18n.php; inlined wp_set_script_translations() in Core/Assets.php. 58 LOC removed.
  • Batch 4 — PHP dead-code deletions (~260 LOC). Removed: Meta\Validator class, Contracts/CheckProvider interface, Block\Registry::unregister_check() + set_check_enabled(), Editor\Registry::register_editor_check_for_post_types(), EditorDetection::get_current_screen() fallback, two orphan actions, one orphan filter. Companion integration-example hot-fix: restored meta-field border styling that a prior commit had stripped.
  • Batch 5 — Extracted ValidationAPI\AbstractRegistry base class. Block/Meta/Editor registries now extend it. Shared defaults merge, required-field validation, warning_msg fallback, level validation, namespace stamping, priority sort, and wp_validation_check_level filter application. Priority validation now consistent across all three scopes (was Block-only).

Polish pass:

  • @example JSDoc blocks on 13 public-API entries (6 store selectors, 5 store actions, useMetaField, useMetaValidation)
  • TypeScript migration started with src/store/constants.ts (typed State, ValidationIssue, BlockValidationResult, MetaValidationResult, ValidationMode, IssueType); deleted stale babel.config.json that was shadowing @wordpress/scripts' default preset
  • Jest unit-test infrastructure + 56 tests covering store reducer, actions, selectors, and issue-helpers. Run with pnpm test.

Documentation refresh:

  • Deleted completed planning artifacts (pass-a, pass-b, pass-c — 1,062 LOC)
  • Rewrote CLAUDE.md, docs/technical/README.md, docs/technical/data-flow.md, docs/gutenberg-alignment/consolidated-plan.md to reflect the post-Batch-1 architecture
  • Created docs/PR-READINESS.md (single-page "where are we on the Gutenberg PR" overview) and docs/guide/troubleshooting.md (10+ common integration issues with diagnostics)
  • Updated docs/PROPOSAL.md, docs/INTEGRATION.md, docs/README.md for consistency with the current implementation

Testing

Environment:

  • WordPress Version: 6.8+ (plugin requires 6.7 minimum)
  • Browser(s) tested: Chrome, Firefox

Test Cases:

For new developer API features:

  • API functions work as documented
  • Hooks/filters fire correctly
  • External plugin integration tested

For developer API updates:

  • Backward compatibility maintained
  • Existing integrations still work
  • New functionality accessible

General testing:

  • No console errors
  • Tested in wp-env environment
  • Works across different WordPress blocks

Automated verification:

  • pnpm test — 56/56 tests pass in ~1s
  • pnpm lint:js — clean
  • pnpm lint:php — clean (PHPCS, 0 violations)
  • pnpm lint:css — clean
  • pnpm build — succeeds, produces expected build/validation-api.{js,css,asset.php}

Manual verification per batch: Each batch was manually verified in a running WordPress instance before commit per the acceptance criteria documented in docs/gutenberg-alignment/consolidated-plan.md. Covered: editor loads cleanly, validation sidebar renders, block/meta/editor checks fire, border classes apply, publish locking toggles correctly, editor.preSavePost aborts saves when errors exist, settings addon round-trips overrides, REST endpoint returns expected response shape.

Screenshots

N/A — internal refactor with no user-facing UI changes. All visible behaviors (sidebar rendering, block indicators, meta-field borders, publish locking) verified unchanged against the pre-refactor baseline.

Developer API Impact (if applicable)

  • No API changes
  • New hooks/filters added
  • Breaking changes to existing API
  • Documentation updated

Detail on the REST path change:

The REST endpoint moved from wp/v2/validation-checks to wp-validation/v1/checks to stop squatting on the core-reserved wp/v2/* namespace. This is a breaking change for any third-party consumer hitting the endpoint directly, but the only known consumer (the validation-api-settings companion plugin) is updated in the same commit set. The final core-facing namespace (wp/v2/validation-checks, wp-block-editor/v1/validation-checks, or similar) is deferred to Gutenberg core review.

Checklist

  • Code follows WordPress standards
  • No PHP/JavaScript errors
  • Tested in wp-env environment
  • Documentation updated

troychaplin and others added 11 commits April 17, 2026 22:04
Remove public API surfaces that had no consumers in the workspace and no
contracts beyond historical intent. ~260 LOC removed; no active consumer
breaks.

- Delete Meta\Validator class (server-side validation helper)
- Delete Contracts\CheckProvider interface (no implementations)
- Remove Block\Registry::unregister_check() + wp_validation_check_unregistered
- Remove Block\Registry::set_check_enabled() + wp_validation_check_toggled
- Remove Editor\Registry::register_editor_check_for_post_types() bulk helper
- Remove EditorDetection get_current_screen() fallback (unreachable)
- Remove orphaned wp_validation_validate_meta filter
- Update technical/hooks.md, technical/api.md, technical/decisions.md,
  guide/README.md, guide/meta-checks.md, guide/examples.md, docs/README.md,
  CLAUDE.md, INTEGRATION.md, PROPOSAL.md
- Delete guide/check-providers.md

Lifecycle hooks kept (documented public API, consumed by settings addon
or reserved): wp_validation_initialized, wp_validation_ready,
wp_validation_editor_checks_ready, wp_validation_check_registered,
wp_validation_editor_check_registered, wp_validation_meta_check_registered,
wp_validation_check_args, wp_validation_editor_check_args,
wp_validation_meta_check_args, wp_validation_should_register_check,
wp_validation_should_register_editor_check,
wp_validation_should_register_meta_check, wp_validation_check_level.

See docs/gutenberg-alignment/pass-c.md for rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 58-LOC I18n class was a wrapper around a single wp_set_script_translations()
call. Per Gutenberg-style functional preference, collapse into Assets.php at the
enqueue site.

- Delete includes/Core/I18n.php
- Assets::__construct() now accepts (string $plugin_file, string $text_domain)
- Assets::enqueue_block_assets() calls wp_set_script_translations() inline
- Plugin::init() drops init_translations() step; passes text_domain directly to
  the Assets constructor
- Update CLAUDE.md project structure diagram

No public API changes.

See docs/gutenberg-alignment/pass-a.md Batch 3 / pass-c.md C-7 for rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pull duplicated registration logic (defaults merge, required-field check,
level validation, warning_msg fallback, namespace stamping, priority sort,
wp_validation_check_level filter application) into a shared abstract base
class. Concrete subclasses keep their own singleton, storage shape,
scope-specific register method, and scope-specific hook names.

- Add includes/AbstractRegistry.php with shared helpers:
  normalize_args(), stamp_namespace(), sort_by_priority(),
  apply_level_filter(), plus DEFAULTS + VALID_LEVELS constants
- Block, Meta, Editor registries now extend AbstractRegistry
- Logger trait methods changed from private to protected so subclasses
  and the abstract can share inherited access

Behavior change (improvement): priority validation now applies to all
three scopes. Previously only Block validated priority; Meta and Editor
silently accepted garbage values. Non-numeric priorities now coerce to
10 uniformly.

Public API unchanged: global function signatures, filter names, action
hook names, REST response shape all identical.

Constants declared without visibility modifiers for PHP 7.0 compatibility
(visibility on class constants requires PHP 7.1+; plugin header declares
7.0 minimum).

See docs/gutenberg-alignment/pass-c.md C-8 for rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stop squatting on the core-reserved wp/v2 namespace. Use a plugin-owned
namespace now; the core-PR can negotiate a final location (wp/v2 or
wp-block-editor/v1) during review.

- ChecksController::$namespace: wp/v2 -> wp-validation/v1
- ChecksController::$rest_base:  validation-checks -> checks
- Final URL: /wp-json/wp-validation/v1/checks
- Update docs: CLAUDE.md, README.md, readme.txt, docs/technical/README.md,
  docs/technical/api.md, docs/technical/companion-package.md,
  docs/INTEGRATION.md, docs/PROPOSAL.md, docs/guide/README.md,
  docs/guide/examples.md
- PROPOSAL.md calls out that final core namespace is TBD during PR

Settings addon updated in a separate commit in its own repo.

See docs/gutenberg-alignment/pass-a.md Batch 2 for rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flatten src/editor/* and src/shared/* into a Gutenberg-package-style tree
so the eventual core-PR lands as a git mv into packages/validation/src/.
All public API surfaces (store name, filter names, global PHP functions,
REST endpoint) are unchanged; this is a pure reorganization plus a few
architectural polish items.

Structure:
  src/index.js          Entry point (replaces src/script.js)
  src/store/            Data store (moved verbatim from src/editor/store/)
  src/utils/            Flat utilities (validate-*, use-invalid-*,
                        use-meta-*, issue-helpers, get-validation-config,
                        use-debounced-validation, use-validation-issues,
                        index.js barrel)
  src/components/       One folder per component
  src/hooks/            Side-effect modules + two React hooks
                        (use-validation-sync, use-validation-lifecycle)

Architectural changes (Pass B):
- ValidationProvider renderless component becomes useValidationSync hook
- ValidationAPI renderless component becomes useValidationLifecycle hook
- Both hooks invoked by renderless sibling wrappers (<ValidationSync />,
  <ValidationLifecycle />) inside the ValidationPlugin root to avoid the
  infinite render loop that arose from subscribing and dispatching to the
  same store within one parent component
- New src/hooks/pre-save-validation.js installs the editor.preSavePost
  async filter as a belt-and-suspenders save gate on top of lockPostSaving

Consolidations (Pass C):
- Extracted useValidationIssues() hook (new src/utils/) to deduplicate
  the 3-selector useSelect block from ValidationSidebar and the
  lifecycle hook
- use-meta-field.js collapses its dual useSelect into one

Getter-style hook files renamed: GetInvalid* -> useInvalid* both in
file names and exported function names, reflecting their nature as
React hooks rather than plain getters.

Build/tooling:
- webpack.config.js: entry points to src/index.js; path aliases
  (@, @editor, @shared) removed in favour of relative imports
- package.json: sideEffects declared for src/index.js, src/hooks/**,
  src/store/index.js, src/**/*.scss; main updated to
  build/validation-api.js

HOC behavior unchanged:
- editor.BlockEdit filter still registers withErrorHandling (now lives
  in src/hooks/validate-block.js as a side-effect module)
- editor.BlockListBlock filter still registers withBlockValidationClasses
  (now in src/hooks/block-validation-classes.js)

Completes the five-batch Gutenberg alignment plan. See
docs/gutenberg-alignment/consolidated-plan.md and pass-a.md for the full
rationale; pass-b.md for the hook conversion and preSavePost decisions;
pass-c.md for the consolidation rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match Gutenberg package convention of showing realistic usage snippets on
public selectors/actions and user-facing hooks. Improves IDE hover
documentation for external plugin authors.

- store/selectors.js: 6 selectors (getInvalidBlocks, getInvalidMeta,
  getInvalidEditorChecks, getBlockValidation, hasErrors, hasWarnings)
  each showing a useSelect pattern
- store/actions.js: 5 actions (setInvalidBlocks, setInvalidMeta,
  setInvalidEditorChecks, setBlockValidation, clearBlockValidation)
  each showing a useDispatch pattern
- utils/use-meta-field.js: TextControl spread example
- utils/use-meta-validation.js: custom render + usage-vs-useMetaField note

Internal helpers (issue-helpers, validate-*) keep their one-line
@param/@return style — consumed by the framework itself, not by
external plugins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename src/store/constants.js to constants.ts and add type definitions
for the store state, validation issues, and block/meta validation
results. Action type constants use `as const` for narrower literal
types, enabling exhaustive switch checks in the reducer. Other modules
continue running as JavaScript via @babel/preset-typescript; they gain
IDE type inference on imports from constants.ts without requiring .ts
migration themselves.

Delete stale babel.config.json (pre-TS two-preset config from March)
that was shadowing @wordpress/babel-preset-default and blocking TS
syntax handling. wp-scripts' default preset now applies as designed
and includes @babel/preset-typescript out of the box. Incidental
bundle-size drop from ~94KB to ~70KB (minified) from the more optimized
preset configuration.

New exported types:
- ValidationMode, IssueType
- ValidationIssue
- BlockValidationResult
- MetaValidationResult
- State

No runtime behavior change. Matches Gutenberg package convention of a
typed store/constants file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover the pure logic layer — store reducer, action creators, selectors,
and the issue-helper factories — with Jest. No mocks required; these are
all pure functions consuming plain inputs.

Test infrastructure is zero-config via @wordpress/scripts (test-unit-js
uses its bundled Jest preset). Added `test` and `test:watch` npm scripts
and an .eslintrc override so Jest globals (describe/it/expect) don't trip
no-undef in __tests__ directories.

Coverage:
- src/store/__tests__/reducer.test.js — default state, all 5 action types,
  unknown-action pass-through, CLEAR_BLOCK_VALIDATION no-op edge case
- src/store/__tests__/actions.test.js — each action creator's shape
- src/store/__tests__/selectors.test.js — all 6 selectors including
  error-precedence logic in hasErrors/hasWarnings and DEFAULT_BLOCK_RESULT
  fallback
- src/utils/__tests__/issue-helpers.test.js — all 8 exports, including the
  PHP-style snake_case to camelCase transform in createIssue

Deferred to a future polish pass (need filter/store mocking):
- validate-block, validate-meta, validate-editor
- Custom hooks (useMetaField, useMetaValidation, useInvalid* hooks)
- Integration / e2e tests via @wordpress/env

Run with `pnpm test` (~1s). 56/56 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark Polish 1-4 done with commit hashes in the consolidated plan. Add
deferred-polish list (5, 5b, 6, 7) with explicit scope, prerequisites,
and target file paths so the next iteration can pick them up cold.

Rewrite TODO.md to reflect the current tree:
- "Completed" now covers naming alignment + five-batch plan +
  post-batch polish 1-4 (each with commit hashes)
- "Remaining" restructured into Testing / Performance / TypeScript /
  Future considerations, with file paths updated to post-Batch-1
  locations (src/hooks/use-validation-*.js, src/store/__tests__/, etc.)
- Cross-links to the consolidated plan for authoritative polish status

Plugin is now in the state described for picking up integration-plugin
work before cycling back to draft the Gutenberg PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…state

Comprehensive doc pass after the alignment + polish work shipped:
- Removed stale planning artifacts whose purpose has been fulfilled
- Rewrote docs that still described pre-Batch-1 architecture
- Added two new docs: a troubleshooting guide for integrating plugins
  and a single-page PR-readiness summary for future-you / collaborators

Deleted (git history preserves them):
- docs/gutenberg-alignment/pass-a.md  (convention audit — work complete)
- docs/gutenberg-alignment/pass-b.md  (architectural audit — work complete)
- docs/gutenberg-alignment/pass-c.md  (leanness audit — work complete)

Rewrote (fixed stale architecture references):
- CLAUDE.md: project-structure tree, data-flow diagram, conventions,
  doc map. Removed src/editor/ + src/shared/ + webpack aliases +
  renderless-component descriptions. Added AbstractRegistry, hooks
  (useValidationSync/useValidationLifecycle), editor.preSavePost gate,
  test + polish script references, TypeScript constants.ts note.
- docs/technical/README.md: JS Layer section rewritten for the hook-
  first architecture. Side-effect modules in src/hooks/ listed with
  their filter registrations. Render-loop sibling-wrapper rationale
  documented. Save-locking defense-in-depth explained.
- docs/technical/data-flow.md: full 14-step walkthrough from PHP
  registration through useInvalidBlocks -> useValidationSync ->
  store -> useValidationLifecycle / pre-save-validation / sidebar.
  Covers AbstractRegistry normalization steps.
- docs/gutenberg-alignment/README.md: points at the three still-useful
  files (consolidated-plan, core-pr-migration, PR-READINESS).
- docs/gutenberg-alignment/consolidated-plan.md: pared down from
  execution playbook to execution record. Per-batch commits + summary.
  Polish status with commit hashes. Deferred items (5, 5b, 6, 7) with
  scope and prerequisites.

Updated (targeted fixes):
- docs/INTEGRATION.md: Component Mapping now lists hooks not renderless
  components; Packages Affected section reflects hook-first architecture;
  editor.preSavePost added to "new to Gutenberg" table; open questions
  refreshed.
- docs/PROPOSAL.md: Reference-Implementation section describes the
  current hook-based design. Added note about the sibling-wrapper
  pattern. State-management description updated.
- docs/README.md (index): adds PR-READINESS + troubleshooting; reorders
  into Start-Here / Developer-Guide / Technical / Core-Merge / Working-
  Notes sections.

Created:
- docs/PR-READINESS.md: single-page "where are we, what's next" for the
  Gutenberg PR. TL;DR, status tables, open questions for core team,
  next-steps checklist for future-you returning after a break.
- docs/guide/troubleshooting.md: 10+ common issues developers integrating
  with the API hit, each with diagnostic steps — sidebar empty, validation
  not firing, REST 401/404, borders missing, save stuck locked, JSON
  response errors, React #185 render loops, env-specific failures.

Build + tests clean (pnpm build + pnpm test both green).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@troychaplin troychaplin merged commit ddd2db8 into main Apr 18, 2026
1 check passed
@troychaplin troychaplin deleted the review/multiple-plugins branch April 18, 2026 17:33
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