Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/rendar-digital/rendar-editorial-controls/archive/refs/heads/main.zipRendar Editorial Controls 0.1.1
Standalone WordPress plugin for optional editorial controls in the block editor. Requires WP 6.6+ (PluginDocumentSettingPanel in wp.editor, block-editor hooks, REST post meta and Settings API) and PHP 7.4+. PHP 7.4 is a lint target, not a full runtime integration environment. The disposable SQLite WordPress acceptance harness is documented in tests/integration/README.md; consult its per-version evidence rather than inferring coverage from the stub suites.
Three independently enabled modules under Settings → Editorial Controls:
- Featured:
_focal_pointobject{x,y}in [0,1] on eligible REST post types supporting both thumbnail and custom-fields, including pages and CPTs when configured. Existing meta is unchanged; toggling off never deletes it. Permission:edit_postper record. The panel is only enqueued for enabled eligible post types. - GB image: optional GenerateBlocks media Settings shortcut (acceptance fixture: GB 2.5.0-rc.1 / Pro 2.8.0-rc.1, both requiring WP 6.7+; the base plugin still supports WP 6.6 without this module) for aspect ratio, scale and a static image object-position picker. Writes only via GB
onStyleChangeto its editable style data; no PHP frontend output. Skips when the GB media editor script is absent or the block/context isn't a supportedgenerateblocks/mediaimage. No automatic featured-image binding. - Tags: post_tag-only editor selector. Typed tokens remain pending/error on lookup/create failure, with serialized requests and bounded exponential backoff, explicit Retry. Uses a server-authoritative lookup before creation;
term_existsassigns the existing term ID. Categories and other taxonomies remain core.
Tag policy: Default exact means literal trimmed name equality (PHP bytes after DB candidate lookup), rather than the old near-duplicate rule. No guessed JS/PHP accent parity; browser keys are literal trimmed tokens, only for queue identity. aggressive is an optional server-side normalization policy: WordPress remove_accents with an explicit en_US locale, Unicode case-folding when mbstring is available, and punctuation/whitespace folding while preserving letters and numbers in every script (including mixed-script names). Only WP-supported accent mappings are equivalent: WP does not map ffi to ffi. Browser keys remain literal queue identities; only the server determines equivalence. It builds a normalized index of at most 50,000 post_tag rows on the first lookup after a relevant change, stored in one fixed non-autoloaded wp_option alongside a small generation option, across HTTP requests even without a persistent object cache. Create/edit/delete and term count updates invalidate the generation. A concurrent invalidation makes an older scan fail closed; a late old writer's payload is ignored on the next lookup, not treated as current. This is not an atomic resolve/create transaction. At 50,001 rows, or 500 equivalent candidates for one key, lookup returns 503 and creation is blocked. A rebuild reads the whole taxonomy and can be costly (and the stored option large) on a 28k-tag site; test latency and storage on a real site before enabling. There is no periodic expiry/rebuild; a cache write failure returns 503 instead of automatically scanning again for another token in the same request. Prepared token LIKE is not a safe substitute for an exhaustive Unicode index. This policy may group meanings you did not intend, and the server cannot guarantee core's own collation/term_exists shares this definition. Neither policy creates when lookup fails or is incomplete.
Development
php tests/php-unit.php && node tests/normalize-queue.mjs && node tests/scope.mjs && node tests/gb-image.mjs; lint with bash bin/php-lint (the script propagates any PHP parse failure). Run bash tests/php-lint.sh, node tests/tags-component.mjs and bash tests/build-zip.sh for the lint, hostile-name component and archive regressions. tests/test-updater.php needs a real WordPress core: set WP_CORE_DIR or run bin/provision-wp-core to fetch CI's pinned version (docs/wp-core-pin.md); it skips itself with a message if WP_CORE_DIR is unset outside CI, and CI hard-fails instead. The Node suites load the production JS; the PHP suite uses isolated WordPress stubs and a fake DB, not an actual WP site. bash bin/build-zip on a clean commit produces dist/rendar-editorial-controls-0.1.1.zip with stable plugin root and no dev/test files. See docs/API.md and docs/featured-gb.md.
Automatic updates
The plugin checks its own public GitHub Releases (releases/latest/download/info.json, cached 12 hours, 1 hour after a failure) and fails quietly. The static Update URI: https://updates.rendar.digital/rendar-editorial-controls header is only an identifier so WordPress.org cannot claim the slug; it is not a server. No credentials or wp-config.php setup are needed. Automatic updates use WordPress's normal Plugins-screen opt-in and cron. To turn update checks off entirely, add define( 'RENDAR_UPDATES_DISABLED', true ); to wp-config.php. Network-activate on multisite to register the hooks across the network. The updater lives in inc/class-rec-updater.php plus one require_once/new pair in the main file; a WordPress.org build removes exactly those.
To cut a release after review: update the plugin header and REC_VERSION together to a stable X.Y.Z, commit and test, create/push tag vX.Y.Z, then publish a non-draft GitHub Release on that tag. .github/workflows/release.yml validates the versions, runs tests and bin/build-zip, and attaches rendar-editorial-controls.zip (root rendar-editorial-controls/) and info.json (slug, name, version, tag, package, requires, requires_php, tested, last_updated, changelog from the matching CHANGELOG.md section). Publishing is a separate decision.
Operational limits
The editor-only panel does not alter rendering. The featured panel stores an object, not CSS. GB may need additional site-specific wiring for dynamic images; the shortcut itself never wires them. A settings switch changes registration/UI, not existing content or meta. Tag queue state is in browser memory only: do not navigate away from a pending/error token without retrying or recording it. Initial assigned IDs without resolved names await the core store fetch. Integration coverage uses a disposable SQLite backend only; no MySQL/MariaDB or real site traffic is implied. Read docs/integration-acceptance.md for executed cases and limitations. Editor asset enqueue remains at priority 20 (after typical core/plugin registration); the GB shortcut explicitly depends on the GB media editor handle. Featured and Tags have no hook dependency on unrelated panels.
License
GPL-2.0-or-later. See LICENSE.