Chore/consistency remediation#7
Merged
Merged
Conversation
Kernel: a declared Feature class that does not implement FeatureInterface now throws FeatureException like a malformed Conditional (same deterministic-developer-error rule, both sides). PluginHeader gates its derived slug through is_valid_identifier, throwing the new InvalidPluginHeaderException. New consumer-contract exceptions complete the lifecycle vocabulary (HookRegistrationException) and replace bare SPL throws (CyclicObjectGraphException). Boot-metrics array shape collapses to one @phpstan-type alias. Docblock accuracy: __toString false-branch, InvalidVersionException scope, convert_to_primitives root-vs-nested asymmetry, Rendering exception formula unified onto {@see}, transaction guarantee owned by the kernel class docblock, PluginBootReport opener per the descriptor grammar, bootstrap guard-string spelling, WHY comment on the TextDomain fallback guess. Bootstrap's @return void stays: PHP 5.6 forbids native : void, so the tag is load-bearing for PHPStan. Assisted-by: Claude Code:claude-fable-5
Renames the five admin field-surface classes *FieldStore -> *FieldSurface (folder Settings/MetaField/Stores/ -> Surfaces/): an industry survey (ACF, MetaBox, CMB2, Carbon Fields, Fieldmanager, Pods, WC core) is unanimous that the Store family names passive persistence, which this codebase already uses it for (KeyValueStore family, NoticeStore, the object-meta repositories beneath these classes). A surface mounts a field group onto one WP admin surface: registration, render, save, CRUD, cleanup. Two prefix rules settle the remaining drift: the Settings prefix marks the descriptor family and types named for it, so the generic SettingsOptionsProviderInterface becomes OptionsProviderInterface; the WooCommerce token spells out everywhere while WP abbreviates, so WCSettingsBuilder becomes WooCommerceSettingsBuilder and DescriptorBackedWCSettingsPage becomes DescriptorBackedWooCommerceSettingsPage (test fixtures included). Frozen namespace roots are untouched; deptrac collectors and PSR-4 mappings needed no changes. ~240 references across 35 files. Assisted-by: Claude Code:claude-fable-5
…and service shapes One wiring verb: register_lifecycle() becomes register_hooks() across the scheduling backends and hook handlers, HookHandlerInterface now declares it, and both facades (Scheduler, HooksService) forward to every member — a composed handler no longer needs out-of-band wiring. One failure channel for framework misuse: unknown named targets throw the new UnknownHookHandlerException / UnknownNoticeStoreException (AbstractRuntimeException family) — add_notice() no longer drops a notice silently via _doing_it_wrong — and AdminNoticeLogger's ctor id check throws the new InvalidNoticeIdentifierException. PSR's InvalidArgumentException stays only where PSR-3 mandates it (level vocabulary). PHPIniSizeConditional validates its byte-shorthand minimum at construction (InvalidConditionalConfigurationException), so a typo'd gate can no longer degrade to always-met. Service-trio trim: HooksService goes final readonly with handlers fixed at construction (the Scheduler pattern); register_handler() and get_handler() are gone; BufferedHookHandler gains DEFAULT_ID. AdminNotice's bool props drop the is_ prefix (dismissible, persistent). ObjectCache::delete() reports bool; both cache generation options use the *_generation vocabulary. Request::wp_parse_args_recursive moves to Arrays::parse_args_recursive (Request.php held nothing else and is deleted). Conditionals ctor summaries normalize to "Constructor.". Assisted-by: Claude Code:claude-fable-5
…en settings wiring ObjectFieldForm owns all four CRUD verbs: store() becomes set(), and get()/has()/delete() move onto the engine with the canonical store-or-revoke and read-semantics docblocks, collapsing four verbatim copies; the three meta-field surfaces and the order surface become one-line delegates. Wiring-time hardening: register_page() throws BackendAlreadyBoundException on a second bind and InvalidSettingsPageException on a page with zero sections in both backends — the authoring-time seam, leaving the render-time capability projection free to empty a page per user. SettingsPage::$sections loses its empty default (declared before the optional $location). MetaBoxPlacement validates screen, context, and priority at construction (InvalidMetaBoxPlacementException, VO family) and exposes get_capability(); the surfaces' priority coercion is gone. The two anonymous filter closures in WordPressSettingsBackend become instance-addressed hook methods third parties can unhook. Docblock alignment: register_page contract reworded backend-neutral; Storage summaries move to third-person indicative; KeyValueStore declares get before set (implementations reordered to match); the protected meta-key helper is resolve_meta_key(); Schema/functions.php signposts its WP-free vs WP-coupled split; the InvalidValueObjectException reason invariant (no template punctuation, reasons end with a period) is recorded and enforced; descriptor invalidity messages carry the terminal period. Assisted-by: Claude Code:claude-fable-5
…align the package Product-field persistence no longer assumes post meta: get/set/has/delete and the panel render go through wc_get_product() and WC_Data meta methods, so the surface follows whatever datastore WooCommerce backs products with. has() keeps its real-stored-value semantics by filtering out the injected meta_id=0 default rows (the same discrimination the before-save strip uses). The three default-injection filters remain postmeta-coupled by nature — the recorded re-entry trigger is a non-postmeta product datastore, which needs a new injection seam only. Family alignment: CRUD addresses (section_id, product_id, field_id) — descriptor first, like every other field surface — and the singular meta_key() resolver is protected. to_yes_no() and the package's functions autoload are gone (Schema's normalize_checkbox_value is the one canonical mapping); stringify_option_labels() and meta_box_row_html() land in Schema/functions.php, deleting the private copies in the builder, product renderer, and both meta-box surfaces. OrderMetaRepository composes MetadataRepository(MetaType::Post) as its non-order fallback instead of inlining it. UnsupportedSettingsPageCapabilityException rebases onto AbstractRuntimeException per the recorded Unsupported* rule. The WC settings backend gains the late-registration logger diagnostic its WordPress sibling has. ProductDataFieldRenderer accepts CustomFieldType registrations (render-only bridge; tab-level custom_renderers win, and the per-field sanitize requirement stays). WC conditionals become final readonly; ProductDataTab opens per the descriptor grammar; save-path @throws parity lands on the post and term surfaces. Assisted-by: Claude Code:claude-fable-5
Support traits replace the copy-paste families: CreatesUsers (six user factories), IsolatesHooks (six wp_filter snapshot/restore blocks — woocommerce imports the infrastructure trait; cross-package test imports resolve through the root autoload-dev), RequiresWooCommerce (four skip guards), and core's NormalizesHookTables (two copies). The three meta-field surface suites inherit eight shared behaviors from ObjectFieldSurfaceContractTestCase (not glob-collected; the order surface stays standalone — extending it would invert package direction in the split mirrors). 26 test files move to mirror src/ within each suite root (Schema concept folders, Backend/, ProductData/, ValueObjects/); Integration tests carry exhaustive Uses* attributes in every package; the 41 silence-golden index.php files leave tests (they never deploy); woocommerce's byte-copy InMemoryObjectMetaRepository imports infrastructure's. Style: the lone createMock becomes a recording logger, falsy-provider twins align, test regions drop, VersionTest's try/catch loops become data providers (+6 cases), OptionsStore combined tests split, surface test locals rename $store -> $surface. Unit suite: 727 tests green (x2 runs, order-independent). Integration in wp-env: 560 tests, two failures pre-existing on the branch — fixed in the next commit. Assisted-by: Claude Code:claude-fable-5
The in-container integration run surfaced two branch regressions the local Unit suite cannot see. DependencyAdminNoticeRenderer queues into a named store from an admin hook, so the unknown-store throw introduced with the failure-channel unification would have fataled every admin request on a mis-wired consumer — the constructor now validates the store name (UnknownNoticeStoreException at wiring time), and the stale _doing_it_wrong test expects the ctor throw. The settings-backend test still counted one autoload filter per section; the page-wide filter registers once, so the expected delta is one. Integration suite in wp-env: 560 tests, 1298 assertions, green. Assisted-by: Claude Code:claude-fable-5
All seven reusable-workflow calls pin one wordpress-configs SHA (7c1ef45c) — the split pin from the mutation fix is gone, and bumping all refs together is the rule. The three missing src index.php guards land; infection source dirs reorder to package-then-path alphabetical; the consumer-smoke scoper configs agree on the bare patchers deref; workflow comments trim to one-sentence WHYs and every job carries a name. Package docs reach the family bar: shared's README names the live packages; infrastructure gains the monorepo boilerplate, a Lineage section, and a subsystem-level 2.0.0 changelog (13 entries replacing the lone merge bullet); woocommerce's composer description and keywords match its actual surface; CONTRIBUTING and the changelog boilerplates use the real packages:<pkg>:changelog:* script names. Four copy-paste recipes document the first consumer tasks against the as-built API: a settings page, a recurring job, a persistent dismissible notice, and a product-data tab. Assisted-by: Claude Code:claude-fable-5
…ation state The stale blocks now describe reality: the i18n block records the five-real-domains + shape-based-patcher contract (the lint guarantee and the scope-time tripwire are two halves of one mechanism — restoring the pre-merge domains would fail WPCS); the local-development section drops the phantom per-package require-dev, names the deptrac cache file, and documents the PHPStan run-from-inside-a-package rule with the 20-error Action Scheduler baseline; the CI block's consumer-hardening claim matches audit.yml; composer-require-checker's WooCommerce-surface allow-list is on the record. Every adjudicated rule from the consistency remediation lands as a decision block: Store-vs-Surface naming (with the industry-survey rationale), the Settings prefix rule, the per-token host-name rule, register_hooks() as the sole wiring verb, the framework-family failure channel (fail at wiring; PSR only where PSR-3 mandates), the service trio's member-identity and readonly rules, the three field-CRUD address tuples with the deferred-unification re-entry trigger, WC-CRUD-only product persistence with the injection-seam trigger, lifecycle exception symmetry, the settled style rules (bool props, VO factories, helper shapes, throw grammar, regionless exceptions, bootstrap's load-bearing @return void, the SettingsField cap), and the test conventions (mirror-src, Support traits, contract case, exhaustive Uses*). Assisted-by: Claude Code:claude-fable-5
configs 4214abd drops the no-layout root fallback from phpstan auto-discovery — the actual cause of the Quality workflow's 198 symbol-not-found errors (misattributed to a phpstan-wordpress 2.x-dev regression; no v2.0.4 wait needed): from the repo root every per-package run was analyzing the whole monorepo. All seven workflow refs bump to the same SHA per the bump-together rule. The AGENTS.md PHPStan guidance inverts accordingly: the from-root sweep is the clean canonical mode (each config analyzes exactly its declared paths — verified, 7/7 No errors); an in-package run is the noisy mode, where layout discovery unions the package tree and the settings/storage configs surface their ~20 Action Scheduler baseline errors. Assisted-by: Claude Code:claude-fable-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
Affected packages
wp-framework-bootstrapwp-framework-sharedwp-framework-corewp-framework-infrastructurewp-framework-woocommerceChecklist
composer changelog:add:<package>(one per affected package)composer quality-checkpasses locally (PHPCS + PHPStan + Deptrac + unit tests)README.mdmajor) are explicitly justified belowBreaking changes / migration notes