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.
5.7 KiB
Known issues
Carried into the Umbra Bookshelf consolidation. Items 1–4 are pre-existing defects inherited from the three merged plugins; item 5 and the two repaired below are noted for traceability.
1. bd_books and bd_series have two incompatible schemas (DEFERRED — unresolved)
The books and library modules both create tables with identical names and different columns.
Umbra_Database::table() returns $wpdb->prefix . 'bd_' . $name, and the
catalogue issues CREATE TABLE {prefix}bd_books with no IF NOT EXISTS
guard:
catalogue wp_bd_books id, title, slug, description, isbn_*, amazon_asin,
goodreads_id, rating_*, created_at, ...
library wp_bd_books id, post_id, pen_name_id, series_id, volume_number,
asin, isbn, publish_date, sort_order, created_at
Same for wp_bd_series (catalogue: name, slug, description …;
library: name, slug, cover_attachment_id, header_attachment_id, blurb, …).
Consequence. Whichever module is activated first wins; the second either
no-ops (IF NOT EXISTS, library) or raises a database error (catalogue's
unguarded CREATE TABLE). Whichever schema exists first is what the other
module then reads and writes against, producing wrong results rather than an
obvious failure.
Why deferred. Resolving it means choosing a canonical schema and migrating live data — a data-migration project, not a rename. Both datasets may hold rows that matter.
Options.
- Library schema becomes canonical; catalogue tables move to
bd_bookshelf_*with an upgrade routine copying existing rows. Requires renaming DB keys, which this consolidation deliberately avoids. - Catalogue schema becomes canonical; library relies on the
bookpost type and postmeta instead ofbd_books. - Delete the catalogue duplicates and keep only the library model.
2. Goodreads and Amazon scraping is implemented twice
- Catalogue:
Umbra_Goodreads_Scraper,Umbra_Amazon_Scraper. - Library:
Umbra_Bookshelf_Helper::refresh_goodreads_data(),::scrape_amazon_ratings().
Both attach to the same cron hooks (bd_bookshelf_goodreads_refresh,
bd_bookshelf_amazon_scrape), so each scheduled run performs duplicate
requests to third-party APIs — wasted quota, faster rate limiting, and two
places to fix a parsing bug. Needs a decision on which implementation survives.
3. assets/images/default-cover.png was missing
Umbra_Bookshelf_Helper returns assets/images/default-cover.png as the
fallback cover, but the repository contained no images at all, so every
cover-less book rendered a broken image.
Repaired during consolidation: a neutral 600×900 placeholder PNG was generated at that path. Replace it with real artwork before release.
4. email/assets/js/admin.js did not exist
Umbra_Simple_Email_Admin::enqueue_assets() enqueued it on every email admin
page, but no such file was ever committed — a guaranteed 404.
Repaired during consolidation: an empty placeholder was created with a comment explaining the history. Add the intended behaviour, or drop the enqueue.
5. Umbra_SEO is never instantiated
Umbra_SEO registers its hooks from a constructor, but the bootstrap does not
construct it — carried over unchanged from the original BD Bookshelf bootstrap.
The class is loaded and its hooks never attach, so the module is inert.
Left as-is because enabling it changes behaviour. Either instantiate it in
Umbra_Bookshelf::init_modules() or delete it.
6. Licence metadata is inconsistent (NOT CHANGED)
Plugin headers declared License: GPLv2 or later while the repository's
LICENSE/NOTICE files (added in 85de97b) and every SPDX header state
Apache-2.0. The consolidated header now says Apache-2.0 to match the SPDX
headers and LICENSE, but this needs a deliberate decision — WordPress
plugin-directory submissions require a GPL-compatible licence.
Fixed during consolidation
Cannot declare classfatal.class-bdm-theme-compatibility.phpdeclaredBD_BookShelfHelper, the same class name as the library plugin's own helper. Latent while the two were separate plugins (it only fired when both were active), but an unconditional fatal once merged. The shim is nowUmbra_Theme_Compatibility, with aclass_alias()preserving the legacy name for the unmodified theme.- Duplicate
bd_bookshelf_import_csvAJAX handler. BothUmbra_AdminandUmbra_Migrationregistered it; the second silently replaced the first. The import page now lives solely inUmbra_Admin(it is the variant wired toadmin/js/umbra-bookshelf-admin.js);Umbra_Migrationkeeps only the preview/run endpoints it uniquely provides. - Duplicate
bd-bookshelf-settingssubmenu. Catalogue and library both claimed the slug, leaving one Settings screen unreachable. Nowumbra-bookshelf-settingsandumbra-bookshelf-library-settings. - Duplicate admin menu. All three modules called
add_menu_page. Now one top-level menu registered by the bootstrap at priority 9. - Colliding asset handles. Catalogue and library both enqueued
bd-bookshelf-admin; the second enqueue was silently discarded. Handles are now unique per module. - Modules could miss their own
initcallbacks. Constructing modules from inside aninitcallback means theirinitregistrations race the hook currently being dispatched. Modules are now constructed onplugins_loaded, behind a re-entrancy guard. - Over-broad asset hook check. The library module's
strpos($hook, 'bd-bookshelf')matched every catalogue and email screen once all slugs shared theumbra-bookshelf-prefix. Now matched against its own slug list.