Files
Bookshelf/umbra-bookshelf/docs/ARCHITECTURE.md
bdm dec732931c Consolidate three plugins into Umbra Bookshelf
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.
2026-10-18 19:55:36 -07:00

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.