Categories
Categories are managed in the admin, not hardcoded.
GET /admin/categories POST /admin/categories
PUT /admin/categories/:id PUT /admin/categories/:id/image
DELETE /admin/categories/:id POST /admin/categories/reconcile
GET /products/categories # storefront — counts Live products only
Product counts are derived at read time from the products table, never
stored, so they cannot drift. The storefront counts only Live products;
the admin counts everything.
products.category holds the category name, so renaming a category
rewrites every product referencing it inside one transaction. Deleting a
category that still has products is refused — move them first. The product
drawer picks from the managed list rather than accepting free text.
POST /admin/categories/reconcile backfills rows for any category string a
product uses that has no row yet — useful after importing data.
Product status
Only Live products reach the storefront. Draft and Archived are
filtered out of every public query, including direct /products/:id lookups,
which 404.