Files
Umbra-Bookshelf/umbra-bookshelf/docs/TASKS.md
bdm 0bd78e587a Resolve the known issues; switch licence to GPL
Closes the three problems that blocked the consolidation and records the rest
as tasks.

Licence: GPLv2 or later (WordPress.org compatible)

  LICENSE is now the full GPLv2 text and NOTICE is gone (it was Apache
  specific). All 101 SPDX headers read GPL-2.0-or-later and every shipped
  header says "GPLv2 or later".

  This also fixes an inconsistency that predated the consolidation: the theme's
  style.css declared GPLv2 while its PHP files carried Apache headers.

  Old/ and archive/pre-umbra-releases/ keep their original Apache headers on
  purpose -- they are frozen snapshots, not shipped code.

Schema: the catalogue schema is canonical for bd_books and bd_series

  Adds Umbra_Schema_Migration. The catalogue and library modules previously
  both created those two tables with different columns, so whichever plugin
  activated first won and the other read and wrote against the wrong shape.

  The migration detects a library-shaped table, RENAMES it aside (never drops
  it), creates the canonical table and copies rows across:

    - book IDs preserved, keeping bd_book_meta, bd_reviews, bd_ratings and
      bd_review_shortlinks valid
    - title/slug/description resolved from the linked post
    - the library's single isbn/asin land on the ebook columns; amazon_asin is
      also set so existing product links keep working
    - series migrated first so books.series_id still resolves
    - post linkage rewritten as _bd_bookshelf_book_id (preserved meta key)
    - publish_date kept as _bd_bookshelf_publish_date (no catalogue column)
    - bd_ratings goodreads/amazon folded into rating_goodreads/rating_amazon
    - rows whose post is gone are skipped with a log line, not turned into
      unusable books
    - duplicate slugs de-duplicated, since catalogue slug is UNIQUE

  It runs before dbDelta, and on admin_init as well as activation, because
  plugin updates do not re-run activation hooks. Ordering matters: dbDelta adds
  columns but never drops them, so pointed at a library-shaped table it would
  graft on catalogue columns and leave a table satisfying neither schema.

  Umbra_Bookshelf_Helper_Install no longer creates bd_books or bd_series.

Scraping: Goodreads only, one implementation

  There were three Goodreads implementations and two Amazon ones; both library
  "scrapers" were empty stubs attached to cron events that fired on schedule to
  do nothing, and both real scraper classes were never called at all.

  Umbra_Amazon_Scraper is deleted and the duplicate HTML parsing is gone from
  Umbra_Rating_Sync, which now delegates to Umbra_Goodreads_Scraper. The two
  library stubs and their cron registrations are removed. One schedule
  survives: bd_bookshelf_sync_ratings, every 12 hours.

  The rating_amazon column is retained (it holds data and is populated during
  migration) and Amazon retail links are unaffected.

  A new rating fetch failure keeps the last known value instead of writing zero.
  The admin ratings notice no longer warns about Amazon scraping.

Umbra_SEO is now instantiated

  It registers its hooks from a constructor and the bootstrap never constructed
  it, so the class was dead code and no SEO output was produced. Its meta, Open
  Graph, Twitter card and sitemap hooks now attach. Completing SEO for generated
  book detail pages is docs/TASKS.md #2.

Assets that 404'd

  - assets/images/default-cover.png now holds a real 512x815 cover
    (WorkingTitleCover_512x815.png). The helper returned this path but the
    repository contained no images at all.
  - email/assets/js/admin.js is committed as an empty placeholder. It was
    enqueued on every email admin page but had never been committed.

Tests

  - tests/schema_migration_test.php: 42 checks against a real database, driven
    through the sqlite3 CLI on a throwaway file. Covers shape detection, row
    mapping, ID preservation, orphan handling, rating folding, idempotency and
    slug collisions.
  - tests/run_all.sh runs every suite.
  - The declared version is now checked against the plugin header rather than a
    literal, so a version bump cannot silently break the suite.

Docs

  - KNOWN-ISSUES.md rewritten: three resolved, three remaining.
  - TASKS.md added: email admin behaviour, book detail page SEO, batching the
    rating sync, reclaiming legacy tables, reconciling the two book models,
    retiring the Old/ archive.
  - SPEC and ARCHITECTURE updated for schema ownership, the migration ordering
    constraint, and the single scraper.

Version 1.1.0.
2026-10-22 09:47:00 -07:00

4.5 KiB

Tasks

Known, agreed work that is deliberately not done yet. Ordered by size, not priority. See KNOWN-ISSUES.md for defects; this file is for intended work.


1. Define the email module's admin behaviour

includes/email/, email/assets/js/admin.js

The admin script is an empty placeholder. It was referenced by Umbra_Simple_Email_Admin::enqueue_assets() but never committed, so the file was created during the consolidation purely to stop the 404.

Nothing in the email admin is currently script-driven, so the page works — it is just plain HTML forms. Define what should be interactive:

  • Subscriber list — search, sort, pagination and bulk delete. The table is already rendered; this is the highest-value addition.
  • Composer — live preview of the rendered newsletter, and a "send a test to me" button that does not touch the real subscriber list.
  • Settings — validate the Gemini API key and the Gmail credentials on save and report the error in place, instead of failing later at send time.

2. Complete SEO for book detail pages

includes/books/class-umbra-seo.php

The SEO module is now active (it was loaded but never instantiated before, so its hooks never attached). It already provides, per book:

  • <meta name="title|description|keywords"> from the bd_book_seo table
  • Open Graph tags (og:type=book, title, description, URL, image)
  • Twitter card tags
  • sitemap weighting driven by the book's rating

Remaining work, since the system generates the book detail pages itself:

  • Populate bd_book_seo automatically. Rows are only written when something calls Umbra_SEO::update_book_seo(), and today nothing does. On book create/update, derive sensible defaults: title as {book title} by {author}, description from the blurb, image from the cover.
  • Schema.org Book structured data. Currently only Open Graph is emitted. JSON-LD with name, author, isbn, aggregateRating, offers and inLanguage is what actually drives rich results.
  • Per-language metadata. bd_book_meta is keyed by book and language, so a translated book needs its own title/description rather than sharing one.
  • Avoid clashing with SEO plugins. If Yoast, Rank Math or SEOPress is active, it will also emit description and OG tags. Detect them and defer, or register with their APIs instead of printing directly.
  • Canonical and hreflang for multi-language book pages.

3. Stop the rating sync blocking on sleep()

includes/books/class-umbra-rating-sync.php

sync_all_ratings() walks up to 999 books and calls sleep(2) between each one, inside a cron run. That is roughly 33 minutes of a blocked PHP worker, and cron jobs that outrun their next scheduled tick compound.

Suggested fix: sync a bounded batch per run (for example 25 books), store the cursor in an option, and let the next 12-hour tick continue from there. Or schedule each book as its own single event.

This predates the consolidation and was left as-is because the agreed change was scoped to dropping Amazon, but it will bite in production.

4. Decide the fate of the set-aside legacy tables

includes/class-umbra-schema-migration.php

Sites that were built on the library schema get their old bd_books and bd_series renamed to bd_books_legacy_library / bd_series_legacy_library rather than dropped, so nothing is lost.

Once an installation has been verified, those tables are dead weight. Add a small admin action that summarises them (row counts) and offers to drop them once confirmed. Until then they are inert.

5. Reconcile the two book data models

includes/books/ and includes/library/

Making the catalogue schema canonical removed the table conflict, but the two modules still model a book differently:

  • Catalogue — a row in bd_books, with its own ISBN/ASIN/rating columns. Not tied to a WordPress post.
  • Library — a book custom post type, with per-post meta such as ebook_url_amazon, goodreads_rating, seo_title.

Nothing currently keeps them in step: editing a book in one place does not update the other. Decide whether the catalogue is the single source of truth with the library reading from it, or whether they stay independent and a sync layer connects them.

6. Remove the historical Old/ and archive/ directories

These hold pre-consolidation plugin snapshots and release zips. They are useful provenance now and will be noise later. The Old/ tree in particular still carries the old Apache licence headers; it is deliberately excluded from the GPL switch because it is a frozen record, not shipped code.