Released: v4.3.3 (admin pages now factory-driven) — built on v4.3.2 (string errata) and v4.3.1 (production-readiness)
Baseline: 8dd249b (v4.3.0 HEAD prior to this work; previous tag a9e0a06 / v4.3.0 itself)
Commits in this patch chain: 8d99c0a (core) → e6db31f (style.css + lockfiles + PHP floor + admin build docs) → the v4.3.1 tag commit (version-string bump + this review summary) → the v4.3.2 tag commit (user-facing copy errata) → the v4.3.3 tag commit (functions.php admin pages refactored to factory delegation, eliminating the remaining drift surface)
Scope: Production fatal fix → matrix consistency → test green → deprecation cleanup → release hygiene → user-facing copy → admin-page factory delegation
| Metric | Before (v4.3.0 / 8dd249b) |
After (v4.3.3) |
|---|---|---|
CLI ./devtb translate |
Fatal (autoloader broken) | Works end-to-end across 14 frameworks |
| Framework matrix | 9 / 9 / 10 / 9 mismatched | 14 everywhere (REST, CLI, admin, file-handler) |
| PHPUnit | 284 tests, 41 errors, 3 failures | 284 tests, 0 errors, 0 failures |
| PHPUnit deprecations | ~118 (implicit-nullable, float, setAccessible) | 0 |
| Python tests | 109 passed | 109 passed (unchanged) |
| Admin lint | 2 errors, 1 warning | Clean |
| Admin build | Passed (with stale fallback string) | Passed |
Stale claude framework references |
7 files | 0 |
| Stale version strings | 13+ @version doc comments + 4 metadata sites |
All synced to 4.3.3 |
| Stale user-facing copy | "9 files", "72 pairs", "110 pairs", "v3.3.0" banner, 11-framework list, plus 4 more 9-framework hardcoded admin tables | All updated (or factory-driven, eliminating the drift surface) |
functions.php admin pages drift source |
4 hardcoded 9-framework tables/lists, 2 hardcoded pair counts | All four call sites now consume DEVTB_Converter_Factory::get_framework_info() |
| Declared PHP floor | 7.4 (EOL'd 2022-11) | 8.1 |
| Security advisories | 1 high (CVE-2026-24765 in phpunit) | 0 (phpunit bumped 9.6.29 → 9.6.34) |
| Admin build step | Undocumented; dist/ gitignored |
Documented in README + soft-enforced via prepack |
Start here — the rest follows from these three foundations:
includes/class-devtb-autoloader.php(new) — the linchpinincludes/wp-function-stubs.php(new) — extracted from the test bootstraptranslation-bridge/core/class-converter-factory.php— new framework metadata helpers
Then ripple-out: devtb-php, tests/bootstrap.php, functions.php, then the API/CLI/File Handler consumers.
Problem. The CLI registered an inline spl_autoload_register that lowercased the namespace separator \ and produced search paths like translation-bridge/converters/class-devtb/translationbridge/converters/devtb-kadence-converter.php for DEVTB\TranslationBridge\Converters\DEVTB_Kadence_Converter. The actual file is class-kadence-converter.php. Every namespaced converter failed to load → ./devtb translate bootstrap kadence ... threw "class not found."
Fix. New file includes/class-devtb-autoloader.php:
- Strips the namespace prefix; keeps only the short class name.
- Lowercases + kebab-cases; strips the conventional
devtb-prefix. - Searches 8 conventional locations (handling both
class-X.phpandinterface-X.phpnaming and theclass-devtb-X.phpconvention used inincludes/). - Special-cases the
-interfacesuffix:DEVTB_Parser_Interface→ fileinterface-parser.php(notinterface-parser-interface.php). devtb_register_autoloader()is idempotent via a static flag.
Wiring.
/devtb-php: replaces the broken inline autoloader./tests/bootstrap.php: registers after Composer autoload (coversDEVTB_Mapping_Enginewhich has no PSR-4-compliant filename)./functions.php: registers insidedevtb_init_translation_bridge()as defense-in-depth so any future converter missed by the glob-loader still resolves at runtime.
Why not Composer classmap. Would work, but requires composer dump-autoload after every converter add/rename — a deployment-time footgun for a WP theme that may be deployed via SFTP/git pull without composer.
Verification. ./devtb translate bootstrap kadence /tmp/smoke.html now completes (was fatal pre-change).
Problem. Once the autoloader started successfully loading converters, the CLI hit a second fatal: Call to undefined function DEVTB\TranslationBridge\Utils\esc_attr() because PHP looks up unqualified function calls inside a namespace then falls back to global — but WordPress's esc_attr doesn't exist outside a WP runtime.
Fix. Extracted ~270 lines of WP function stubs from tests/bootstrap.php into includes/wp-function-stubs.php. Both the CLI entrypoint and the PHPUnit bootstrap now load the same file. Coverage expanded to include is_wp_error, wp_schedule_single_event, the *_IN_SECONDS constants, sanitize_key, wp_unslash, absint, etc. — everything the runtime actually touches.
All function definitions are guarded with function_exists / class_exists / defined, so the file is safe to load alongside a real WordPress environment.
tests/bootstrap.php shrinks from 380 lines to ~38 (the rest now lives in the shared file).
Source of truth. DEVTB_Converter_Factory::get_supported_frameworks() already returned the 14 correct slugs. Added three constants on the factory + one helper:
public const FRAMEWORK_DISPLAY_NAMES = [ /* slug => display name */ ];
public const FRAMEWORK_FORMATS = [ /* slug => html|json|shortcodes|block */ ];
public const FRAMEWORK_FILE_EXTENSIONS = [ /* slug => extension */ ];
public static function get_framework_info(): array; // computed: name + cms_version + format + extension + file_extensionsThe new helper is the single source-of-truth for REST + CLI + future admin sync. CMS versions are pulled live from each converter's get_target_cms_version().
Consumers rewired:
| File | Before | After |
|---|---|---|
/includes/class-devtb-api-v2.php |
Hardcoded 9-framework array; emitted type+extension; total_frameworks=9, pairs=72 |
Lazy-init from factory in constructor; emits format+file_extensions (matches test spec); auto-computed 14/182 |
/includes/class-devtb-cli.php |
Hardcoded 9-framework map 'bootstrap' => 'Bootstrap 5.3.3'... |
Populated in constructor via build_framework_labels() from factory |
/includes/class-devtb-file-handler.php |
9 entries + bogus 'claude' |
14 entries; detect_framework() claude-branch removed and replaced with kadence-detection |
/includes/class-devtb-config.php |
9 + 'claude', stale display names, VERSION='3.2.1' |
14, accurate display names, VERSION='4.3.0' |
/admin/src/types/index.ts |
10 entries incl. 'claude' |
14 entries |
/admin/src/components/Layout/FrameworkSelector.tsx |
10 rows incl. 'claude' |
14 rows; added TODO to source from /devtb/v2/frameworks for auto-sync |
/admin/src/components/Monaco/MonacoEditor.tsx |
Record<Framework,string> with 'claude' entry |
14-entry Monaco language map |
/includes/class-devtb-wpbakery-templates.php |
Compatibility-score map with 'claude' => 0.95 |
Updated to 13 framework targets (drop claude, add divi-5/elementor-4/oxygen-6/kadence/thrive) |
Tests touched in lock-step (would have flipped green→red otherwise):
tests/Unit/APIv2Test.php—assertCount(9, …)→ 14; expected-frameworks list → 14tests/Unit/CLITest.php— same shapetests/Integration/TranslationBridgeIntegrationTest.php— three'claude'targets repointed togutenberg;test_all_frameworks_as_targetsnow exercises all 13 non-bootstrap targets
API contract change. GET /wp-json/devtb/v2/frameworks framework records:
- Before:
name, type, extension, description - After:
name, description, format, extension, file_extensions, cms_version
The format + file_extensions keys match what APIv2Test::test_framework_info_structure (line 207-208) explicitly asserts. Per Explore: no external consumer was found that binds to the old type key, so this is safe to flip.
Trap. get_status() previously returned only version: '2.0'. The test test_status_endpoint_returns_api_info asserts $data['api']['name'] === 'devtb' and $data['api']['version'] === 'v2'. Added an api: { name, version } sub-object alongside the existing version field so we don't break any other client that reads version at the top level.
Trap. get_job_status error code was 'job_not_found'; test (line 194) expected 'devtb_job_not_found'. The devtb_ prefix matches the codebase's other error-code conventions (devtb_auth_missing_key, etc.), so the test was right.
Bug. parse_arguments() greedily consumed the next arg as a flag value whenever a long flag wasn't followed by an option-prefixed arg. So --dry-run divi became options['dry-run'] = 'divi' instead of true, and divi was dropped from positionals. Test test_mixed_positional_and_options_parsing (CLITest.php:199) caught this.
Fix. Added const BOOLEAN_FLAGS enumerating 10 known boolean long-flags (dry-run, debug, verbose, ai-ready, force, help, version, quiet, no-color, json-output). For long flags in the set, the parser sets true and never consumes the next arg.
Considered and rejected: Adding a BOOLEAN_SHORT_FLAGS set. -d is the short for both --debug (boolean) and --output-dir (value) — call sites:
class-devtb-cli.php:396:has_option('debug', 'd')class-devtb-cli.php:448:get_option('output-dir', 'd')
Treating -d as strictly boolean would break -d /path/to/dir usage. Disambiguating short flags requires per-command schemas, which is out of scope. Short-flag parsing stays greedy with an explanatory inline comment. The failing test only uses long flags, so this is enough.
Bug. Test test_get_extension_returns_correct_extension (FileHandlerTest.php:121) called get_extension('file.txt') expecting 'txt' (filesystem-extension extraction), but the production method does framework-slug → extension map lookup, returning 'html' (the default fallback).
Fix. Did NOT change get_extension() semantics — it has production callers in output-filename generation that depend on framework lookup. Added a new method get_file_extension(string $filename): string that does pathinfo($filename, PATHINFO_EXTENSION) and lowercases the result. Pointed the test at the new method.
Missing methods (errored in PHPUnit). Added:
format_file_size(int $bytes): string— produces0 B,1 KB,1.5 KB,1 MB, ... per the test's expected values; usesfmod($value, 1.0) === 0.0to drop decimal point when whole; trims trailing zeros otherwise.find_files(string $dir, string $pattern = '*'): array— thin alias of the existinglist_files()(preserves test intent without duplicating implementation).
Folded into Step 1b. The shared wp-function-stubs.php now provides:
- In-memory
$GLOBALS['__devtb_stub_transients']backingget_transient/set_transient/delete_transient. current_time($type)returningtime()fortimestamp/U, otherwise aY-m-d H:i:sstring (or GMT variant if$gmttruthy).
The same arrangement covers $GLOBALS['__devtb_stub_options'] for get_option/update_option/delete_option.
20+ APIv2Test/AuthTest/RateLimiterTest errors that originated from these missing functions are now passing.
Bug. Tests called $this->auth->generate_api_key('test_user') with a string, but the signature is int $user_id. The fix is to change the test arguments to integers (which is what WordPress user IDs are semantically).
Bonus discovery. generate_api_key() returns an array ['key' => ..., 'user_id' => ..., 'name' => ..., 'permissions' => [...], ...], not a string. The test asserted assertIsString($key). Updated the test to assert array shape + extract $result['key'] for the string assertions.
Mechanical pass:
/devtb-php: header comment "10 page builder frameworks" → "14";@version 4.0.0→ 4.3.0;define('DEVTB_VERSION', '4.0.0')→ 4.3.0 (also wrapped in!defined()guard)./admin/package.json:0.0.0→4.3.0./includes/class-devtb-config.php:VERSION = '3.2.1'→'4.3.0';@versiondoc → 4.3.0./includes/class-devtb-visual-interface.php: fallback'3.2.2'→'4.3.0'./includes/class-devtb-api-v2.php+class-devtb-file-handler.php+ 11 other includes:@versiondoc → 4.3.0./composer.json: added"version": "4.3.0".
All define() calls in devtb-php and tests/bootstrap.php wrapped in !defined() guards to silence the PHP 9 "constant already defined" deprecation when CLI bootstrap and theme bootstrap intersect.
Three findings, three fixes:
-
admin/src/components/Layout/Toolbar.tsx:119—(window as any).devtbData?.version || '3.3.0'. Createdadmin/src/types/wp-globals.d.tswith a typedWindow.devtbDatadeclaration matching the PHPrender_page()payload exactly. Cast removed. Fallback updated to'4.3.0'. -
admin/src/services/api-client.ts:157—case 429:body hadconst retryAfter = ...causingno-case-declarations. Wrapped the body in{ … }to scope the binding. -
admin/src/components/SideBySideEditor.tsx:28—useEffecthad missing dependenciesisTranslating+translateCode. Verified thattranslateCodeis a Zustand store action (stable reference) and that addingisTranslatingto deps would re-fire the effect every time the in-flight flag toggled (potential debounce-amplification loop).Refactored: the effect now reads both
translateCodeand the in-flight guard viauseEditorStore.getState()at fire-time, so neither is a dependency. The destructure still pullsisTranslatingfrom the store hook because the JSX further down ({isTranslating && <Translating…>}) does need to react to changes.
Implicit-nullable params (typed param with default null, no ?):
class-devtb-rate-limiter.php:159—int $duration = null→?int $duration = nullclass-devtb-corrections.php:522—array $auto_fix = null→?array $auto_fix = null
Note: most matches from initial scan were untyped $default = null patterns which aren't deprecated. Only typed params trigger the warning.
Float-array-key deprecation. PHP 8.5 deprecates silent float-to-int casting on array keys. Found three converters using float literals as keys for percentage→column-type mapping. Changed to string keys with explicit (float) cast at the comparison site:
class-divi-converter.php:508-517class-wpbakery-converter.php:504-521class-avada-converter.php:455-468
Pattern:
// Before (66.66 silently becomes int(66) — wrong + deprecation warning):
$map = [ 66.66 => '2_3', 33.33 => '1_3', ... ];
foreach ($map as $pct => $type) { $diff = abs($width - $pct); ... }
// After:
$map = [ '66.66' => '2_3', '33.33' => '1_3', ... ];
foreach ($map as $pct => $type) { $diff = abs($width - (float)$pct); ... }ReflectionMethod::setAccessible() (deprecated as no-op since 8.1): Removed 18 call sites across tests/Unit/AuthTest.php and tests/Unit/CLITest.php via perl -ne 'print unless /->setAccessible\(true\);/'. All target reflection on properties or methods that are publicly-accessible-by-default in 8.1+.
Final deprecation count during full vendor/bin/phpunit run: 0.
includes/class-devtb-autoloader.phpincludes/wp-function-stubs.phpadmin/src/types/wp-globals.d.ts
- PHP bootstrap:
devtb-php,functions.php,composer.json,tests/bootstrap.php - Factory:
translation-bridge/core/class-converter-factory.php - Consumers:
includes/class-devtb-api-v2.php,class-devtb-cli.php,class-devtb-file-handler.php,class-devtb-config.php,class-devtb-visual-interface.php,class-devtb-wpbakery-templates.php - Deprecation cleanup:
class-devtb-rate-limiter.php,class-devtb-corrections.php, three converters (divi,wpbakery,avada) - Doc-version bumps (11 files):
class-devtb-auth.php,class-devtb-claude-api.php,class-devtb-element-registry.php,class-devtb-encryption.php,class-devtb-job-queue.php,class-devtb-logger.php,class-devtb-persistence.php,class-devtb-webhook.php,class-devtb-wpbakery-advanced.php(and the consumers above also had their@versionbumped) - Admin:
package.json,src/types/index.ts,src/components/Layout/FrameworkSelector.tsx,src/components/Layout/Toolbar.tsx,src/components/Monaco/MonacoEditor.tsx,src/components/SideBySideEditor.tsx,src/services/api-client.ts - Tests:
tests/Unit/APIv2Test.php,AuthTest.php,CLITest.php,FileHandlerTest.php,tests/Integration/TranslationBridgeIntegrationTest.php
# PHP syntax across the repo
find . -name "*.php" -not -path "./vendor/*" -not -path "./node_modules/*" -print0 \
| xargs -0 -n1 php -l 2>&1 | grep -v "No syntax errors"
# → empty outputvendor/bin/phpunit
# → OK (284 tests, 4133 assertions)php -d error_reporting=E_ALL vendor/bin/phpunit 2>&1 | grep -ic deprecated
# → 0python3 -m pytest tests/python -q
# → 109 passedcd admin && npm run lint
# → ESLint: No issues found
cd admin && npm run build
# → built in 539ms; bundle 97.87 kB gzip./devtb --version # DevelopmentTranslation Bridge v4.3.0
./devtb list-frameworks # Supported Frameworks (14 Total); pairs=182
./devtb translate bootstrap kadence /tmp/smoke.html --dry-run # ✓
./devtb translate bootstrap thrive /tmp/smoke.html --dry-run # ✓
./devtb translate bootstrap oxygen-6 /tmp/smoke.html --dry-run # ✓
./devtb translate bootstrap divi /tmp/smoke.html --dry-run # ✓ (--dry-run honored, no greedy consumption)grep -rn "'claude'" --include="*.php" --include="*.ts" --include="*.tsx" includes admin/src translation-bridge tests
# → no matches (full purge confirmed)The bulk of the work above landed in 8d99c0a. Two smaller commits followed before tagging v4.3.1:
This commit closed the remaining "production-clean" gaps after the core fixes shipped:
style.csstheme header — Version4.2.0→4.3.0; Description rewritten to enumerate all 14 frameworks (was 11); dropped the staleclaudetag from the Tags line;Requires PHP: 7.4→8.1. WordPress reads this header to display the theme version, so it had to match.- PHP floor: 7.4 → 8.1. Bumped in three places that act as the version gate:
composer.json"php": ">=8.1"devtb-phpversion_compare(PHP_VERSION, '8.1.0', '<')runtime check- README install prerequisites Rationale: PHP 7.4 EOL'd 2022-11 and 8.0 EOL'd 2023-11. Static analysis confirmed no PHP 8-only syntax was actually in use, so 7.4 still technically worked — but staying there masked the fact that the codebase is only tested against 8.5.3 and will accumulate deprecation noise as more 8.x rules land. 8.1 has security support through 2025-12. User-confirmed decision.
composer.lockregenerated — beyond just refreshing the content-hash,composer updatesurfacedCVE-2026-24765(Unsafe Deserialization in PHPT Code Coverage Handling, severity high) affecting phpunit 9.6.29. Bumped to 9.6.34 (still in the existing^9.5constraint range — no manual constraint change needed). Pulled along:doctrine/instantiator 2.0.0 → 2.1.0,nikic/php-parser v5.6.2 → v5.7.0,sebastian/comparator 4.0.9 → 4.0.10.composer auditis now clean.admin/package-lock.jsonregenerated — picked up theadmin/package.jsonversion bump to 4.3.0 (root project version, not transitive deps).admin/package.jsonrelease scripts — added two:"prepack": "npm run build", "release-build": "npm ci && npm run lint && npm run build"
prepackis npm's lifecycle hook fornpm pack, so anyone bundling a release tarball automatically rebuilds the React UI.release-buildis a manual one-liner for release engineers (clean install + lint + build).- README.md install section — added the
cd admin && npm ci && npm run buildstep explicitly, sinceadmin/dist/is gitignored (verified viaadmin/.gitignore:11). Bumped PHP requirement to 8.1+; added Node 20+ to prereqs.
Mechanical pass syncing every version reference for the v4.3.1 tag, plus this CODEX_REVIEW.md update folded in so the review summary ships inside the tag. 30 version-string files touched, all single-string substitutions:
style.cssVersion: 4.3.0→4.3.1composer.json,package.json,admin/package.jsonversionfieldcomposer.lock+admin/package-lock.jsonregenerateddevtb(bash wrapper)VERSION="4.3.0"→"4.3.1"devtb-phpdefine('DEVTB_VERSION', '4.3.0')→'4.3.1'(guarded by!defined())functions.phpDEVTB_THEME_VERSIONconstanttests/bootstrap.phpDEVTB_VERSIONconstantincludes/class-devtb-config.phpVERSIONconstincludes/class-devtb-visual-interface.phpfallback string at render_page line 297admin/src/components/Layout/Toolbar.tsxfallback string- All
@versionPHP doc comments acrossincludes/*.phpandfunctions.php(24 doc comments)
Trap caught. The CLI's reported version comes from the bash wrapper devtb line 25, not from the PHP DEVTB_VERSION constant. Without bumping the bash wrapper, ./devtb --version would still print v4.3.0 even after every PHP-side bump. Re-running the smoke test caught it before commit.
After the version-bump commit landed, git tag -a v4.3.1 was created and pushed. gh release create v4.3.1 published the GitHub release at https://github.com/coryhubbell/Development-Translation-Bridge/releases/tag/v4.3.1 with notes covering the patch and breaking-change callouts. The tag was amended once to fold this review summary into the tagged commit, so CODEX_REVIEW.md ships inside the tarball.
If you only have time to look at three things:
-
includes/class-devtb-autoloader.php— does the location-search list cover all naming conventions? Are there edge cases (e.g., a future converter with noDEVTB_prefix in its class name)? The-interfacesuffix special-case is the trickiest bit. -
translation-bridge/core/class-converter-factory.php::get_framework_info()— instantiates every converter to readget_target_cms_version(). This is called fromDEVTB_API_V2::list_frameworks(public REST endpoint,permission_callbackis__return_true). Is the instantiation cost acceptable, or do we want a staticFRAMEWORK_CMS_VERSIONSconstant? -
includes/class-devtb-api-v2.php::list_frameworks— the response shape changed (type→format, addedfile_extensions). Confirm no external consumer (Postman collections, partner integrations, docs) depends on the old keys. The admin UI doesn't currently consume this endpoint (theFrameworkSelectoris still hardcoded), so the only known consumer is the PHPUnit suite.
-
includes/class-devtb-claude-api.phpstill references a non-existent unnamespacedTranslatorclass on lines 84 and 335. This file was broken before this change — no caller exercises those methods (onlyis_api_available()andget_corrections()are reached fromclass-devtb-corrections.php). Out of scope; would be a separate ticket. The'claude'framework slugs inside that file are now meaningless but harmless since the methods are unreachable. -
Admin
FrameworkSelectorstill hardcodes the 14-framework array. A TODO comment was added pointing to/devtb/v2/frameworksas the eventual source of truth. Same forMonacoEditor's language map. Per the plan: "no new abstractions unless they're a clear win" — wiring up runtime fetch + loading states is a meaningful refactor. -
Browserslist data is 6 months old, baseline-browser-mapping is 2 months old. Vite emits a soft warning. Updating these is a one-line npm command but it's a dependency-floor change that should be its own commit.
-
Short CLI flags (
-d,-v) remain greedy. Long flags got aBOOLEAN_FLAGSschema in Step 3 to fix--dry-run divigreedy-consumption, but short flags stayed greedy because-dis the short for both--debug(boolean) and--output-dir(value-taking). Disambiguating short flags requires per-command argument schemas, which is out of scope. Inline comment onclass-devtb-cli.php:185-195explains the trade-off.
This file was first written against commit 8d99c0a alone. Since then:
- PHP floor bumped to 8.1 — supersedes the original assumption that 7.4 was fine to keep. See the
e6db31fsection above. - CVE-2026-24765 cleared — surfaced and resolved during the
composer.lockregeneration. Was not present in the original commit. - Admin build step is now documented + soft-enforced — the original
8d99c0aleft this as a known gap.e6db31fcloses it. - Versions are now 4.3.3 — every
4.3.0/4.3.1/4.3.2reference outside CHANGELOG/release notes is now4.3.3. - User-facing copy errata fixed in v4.3.2 — see the section below.
- Admin pages now factory-driven in v4.3.3 —
functions.phpno longer hardcodes framework lists anywhere; all four admin call sites delegate toDEVTB_Converter_Factory::get_framework_info(). See the v4.3.3 section below.
The Step-by-Step Change Index (Steps 1-9) above still describes the core fixes accurately; only the surrounding metadata moved.
External code review (post v4.3.1) flagged three pockets of stale user-facing text that the matrix-consistency pass missed because they live in human-language strings rather than data structures:
style.csstheme header / comment block (lines 16-31, 54). The long description still enumerated 11 frameworks, claimed "110 translation pairs (11 frameworks x 10 targets)", and the ASCII banner read "Translation Bridge System v3.3.0". WordPress shows this block in the Themes admin UI. Updated to: 14-framework list, "182 translation pairs (14 frameworks x 13 targets)", and the v4.3.2 banner.functions.phpadmin help table. Thetranslate-allrow described its output as "9 files" (line 315), and the Framework Details card under the admin menu showed "Translation Pairs: 72 (9 frameworks x 8 targets)" (line 399). Both now reflect 14 / 182.
No code or test behavior changed — these are strings displayed to operators in the WP admin and theme-listing UI. Confirmed via:
grep -rnE "9 files|72 pairs|110 pairs|11 frameworks|v3\.3\.0" --include="*.php" --include="*.css" --include="*.md" .
# → no matches outside vendor/, node_modules/, CHANGELOG, and historical release notesThis release is a forward commit on main (no history rewrite) and a normal annotated tag (v4.3.2).
Post-v4.3.2 review surfaced that functions.php still had four more hardcoded 9-framework tables that v4.3.2 missed. Rather than chase strings a third time, the v4.3.3 commit refactors the affected admin pages to delegate to the converter factory so the drift surface is eliminated.
Refactored call sites in functions.php:
- Admin home → "Supported Frameworks" card. Was a static
<ul>of 9 hardcoded<li>entries + the string "72 Translation Pairs". Now loops\DEVTB\TranslationBridge\Core\DEVTB_Converter_Factory::get_framework_info()and derives the count fromcount(...). - Frameworks admin page → "Translation Matrix" table. Was a hardcoded
$frameworksmap of 9 + a parallel$formatsmap of 9. Now a singleforeachover factory data, with the table gaining a "Target CMS" column (was format only). - Frameworks admin page → "Framework Details" card. Was the string "Translation Pairs: 182 (14 frameworks x 13 targets)" baked in from v4.3.2. Now computed live:
$framework_count * ($framework_count - 1). - Settings admin page → "Default Source Framework"
<select>. Was 9 hardcoded<option>tags. Now loops the factory and honors the saved option viaselected(). - System Status table → "Supported Frameworks" / "Translation Pairs" rows. Were the literal strings
9 (Bootstrap, DIVI, ..., Oxygen)and<td>72</td>. Now derived from the factory.
Also fixed in this commit:
- PHP version display + gate. Both the System Status row and the admin-notice gate still compared against
7.4.0despite the v4.3.1 PHP floor bump. Bumped both to8.1.0so the displayed requirement matchescomposer.json.
Why the refactor instead of more string edits: the same 9-framework copy had now resurfaced in five separate admin contexts across two patch passes. The pattern was clearly going to keep biting. Replacing the hardcoded data with a factory call removes the divergence risk for these surfaces entirely — adding a 15th framework later only requires updating the factory, not hunting down admin templates.
Net effect: zero hardcoded framework slugs or counts remain in functions.php. The Settings select even honors the user's previously-saved choice via selected(), which it didn't before.
No behavior changes to user-visible flows beyond accurate counts/labels. PHPUnit suite stays at 284 / 4,133 passing.