Merge BD Bookshelf (0.1.0), BD BookShelfHelper (2.0.0) and BD SimpleEmail
(2.0.0) into a single plugin released as Umbra Bookshelf 1.0.0, with the
BD Bookshelf theme left as a separate, unrenamed artifact.
Modules
includes/books/ catalogue, ISBN/ASIN, series, retail, ratings, shortcodes,
SEO, GDPR, scrapers, admin, CSV/post import
includes/library/ book post type + series taxonomy, pen names, data entry,
shortlinks, reviews, migrate
includes/email/ subscribers, newsletters, composition, sending
Renamed: plugin headers, text domains (all three -> umbra-bookshelf), classes
(BD_*/BDM_* -> Umbra_*), constants (-> UMBRA_BOOKSHELF_*), asset handles, JS
globals and admin slugs.
Deliberately not renamed -- persisted contract with live installs and with
published post content: table names, option keys, post meta keys, transients,
nonce actions, cron hooks, AJAX actions, shortcodes ([bd-book], [bd-books],
[bdm_signup]), post type `book`, taxonomy `series`, cookies and CSS classes.
BD_BookShelfHelper survives only as a class_alias for the unmodified theme.
Collisions resolved
* class-umbra-theme-compatibility.php declared BD_BookShelfHelper, the same
name as the library module's helper. Latent while the plugins were
separate (only fired when both were active), but an unconditional fatal
once merged. Renamed to Umbra_Theme_Compatibility with a class_alias.
* All three modules called add_menu_page. One top-level menu is now
registered by the bootstrap at admin_menu priority 9.
* BD Bookshelf and BookShelfHelper both claimed bd-bookshelf-settings, leaving
one Settings screen unreachable -> umbra-bookshelf-settings and
umbra-bookshelf-library-settings.
* Catalogue and library both enqueued bd-bookshelf-admin; the second enqueue
was silently discarded -> unique handles per module.
* Umbra_Admin and Umbra_Migration both registered bd_bookshelf_import_csv, so
one handler silently replaced the other. The import page now lives only in
Umbra_Admin (the variant wired to the admin JS); Umbra_Migration keeps only
the preview/run endpoints it uniquely provides.
* Modules were constructed from an init callback, so their own init
registrations raced the hook being dispatched. Now constructed on
plugins_loaded behind a re-entrancy guard.
Also
* Missing assets created: assets/images/default-cover.png (referenced but
never committed, so every cover-less book 404'd) and email/assets/js/
admin.js (enqueued on every email admin page but never committed).
* Library module's strpos($hook, 'bd-bookshelf') asset check matched every
catalogue and email screen once slugs shared the umbra-bookshelf- prefix;
now matched against its own slug list.
* Consolidated uninstall deduplicates the double drop of bd_books/bd_series.
* Specs aligned to the new name; pre-merge specs retained under
spec/archive/ with provenance notes.
* Tests added: tests/verify_structure.py (static) and tests/smoke_runtime.php
(WordPress-stub runtime boot, front-end and admin contexts).
Deferred and recorded in docs/KNOWN-ISSUES.md: the catalogue and library
modules still create bd_books and bd_series with two incompatible schemas
(pre-existing, now unconditional), and Goodreads/Amazon scraping is still
implemented twice on shared cron hooks.
3.3 KiB
Architecture
Why one plugin
Three plugins shipped the same product surface under three admin menus and shared two cron hooks. Users installed, updated and deactivated them independently while the code assumed all three were present. Consolidating into one plugin makes the dependency explicit and lets one activation hook set up a consistent state.
The trade-off is that three independent add_menu_page calls, three sets of
admin assets and three activation routines now share a single namespace. Every
one of those collided, and each collision is recorded in KNOWN-ISSUES.md.
Layers
umbra-bookshelf.php (bootstrap)
├── constants one UMBRA_BOOKSHELF_* set
├── requires 21 class files, three modules
├── plugins_loaded construct modules
├── init text domain
└── admin_menu(9) single top-level menu
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
includes/books includes/library includes/email
Umbra_Database Umbra_Bookshelf_Helper Umbra_Simple_Email
Umbra_Book_Manager …_Helper_Admin …_Admin
Umbra_Authors …_Helper_Install …_Install
Umbra_Series Umbra_Email_Sender
Umbra_Retail_Sites
Umbra_Rating_Sync
Umbra_Shortcodes
Umbra_SEO assets: admin/, public/, library/, email/
Umbra_GDPR assets/
Umbra_Goodreads_Scraper
Umbra_Amazon_Scraper
Umbra_Admin
Umbra_Migration
Umbra_Theme_Compatibility -> class_alias BD_BookShelfHelper
Design rules
One owner per name. Every class, constant, asset handle, menu slug and AJAX action is owned by exactly one module. The bootstrap owns the top-level menu and the shared constants; modules own their submenus and assets.
Persistence is not part of the identity. Anything written to the database or embedded in published content keeps its historical name. The rename covers headers, text domains, classes, constants, handles, slugs and JS globals — never table names, option keys, meta keys, cron hooks, nonces, shortcodes, post types, taxonomies or cookies. See §2.2 of the specification for the full list.
Registration order is explicit. The top-level menu registers at
admin_menu priority 9 so the parent exists before modules append submenus at
the default 10. Modules construct on plugins_loaded so their own init
callbacks are registered before init fires.
Compatibility shims are explicit. Umbra_Theme_Compatibility exposes the
static API the unmodified theme calls, and re-exports the legacy
BD_BookShelfHelper name via class_alias().
Not resolved here
The catalogue and library modules keep two independent data models that happen
to share table names bd_books and bd_series. Consolidation made them
unconditionally conflict; it did not make them agree. Resolving this is the next
piece of work — see KNOWN-ISSUES.md §1 for the options.