Subsystem 24 — Reviews
Purpose
Product reviews and ratings with a real moderation queue — the “bare basics” every commerce
site needs (see .pHive/planning/epic-backlog.md epic 49’s own reversal of the earlier call that
this was community-plugin territory, not core). A shopper submits a review, it starts pending,
and it only ever reaches a PDP once an admin explicitly moderates it to published.
Depends on
@mercatus-liber/core only (for the EventBus). Never imports catalog, marketing-catalog,
cms, or any other subsystem — a review’s productId is a bare string, resolved by the
app-composition layer, the same thin-coupling posture recommendations/advertising use.
Responsibilities
Reviewentity: id, productId, rating (1-5), authorName, title, body,verifiedPurchase(defaults false),status(pending/published/rejected), createdAt.ReviewsService.submitReview— always landspending, regardless of caller; there is no “auto-publish” path anywhere in this package. Publishesreviews.review.submitted.ReviewsService.listPublishedReviewsForProduct— the real public PDP read path; filters topublishedonly, so a pending or rejected review can never leak onto a live product page no matter who calls it.ReviewsService.listAllForModeration— the admin queue’s read path, every status, optionally filtered by one status.ReviewsService.moderateReview(id, "published" | "rejected")— the only way a review’s status changes after submission. Publishesreviews.review.publishedonly on the published transition (never on rejection).ReviewsService.getRatingSummary(productId)— average + full 1-5 distribution, computed fresh fromlistPublishedReviewsForProducton every call, never cached or stamped (same “always live” posture asPromotionsService.evaluate).ReviewRepository(adapter pattern) + in-memory reference implementation.
App-layer wiring (apps/reference-storefront)
- PDP (
app/demo/[demoSlug]/products/[slug]/page.tsx): rendersgetRatingSummary+listPublishedReviewsForProduct— a zero-count summary and an empty reviews array render byte-for-byte what a PDP looked like before this subsystem existed (the review section and “write a review” form are additive, not gating). - Shopper submission:
submitReviewAction(lib/actions.ts) — a plain form action, no authentication required (matches every other real e-commerce review form). - Admin moderation:
/admin/reviews— every review regardless of status, with Publish/Reject actions per row, gated by the samerequireAdminPermission("mutate")guard every other admin mutation carries. Product names/links are resolved fromCatalogService.getProductat render time (a review only stores the bareproductId). - Seed content: all 3 demo stores (
lib/seed.ts/seed-northline.ts/seed-broadleaf.ts) seed a real mix of published and (deliberately) still-pending reviews via the actualsubmitReview→moderateReviewpath, never fabricated pre-published records, so the moderation queue always has real pending work to demonstrate.
Explicitly NOT this subsystem’s job
- Verified-purchase enforcement —
verifiedPurchaseis a plain caller-supplied boolean today; no order-history cross-check exists. A real deployment wiring this to actual order data is a documented future integration point, not a silent gap. - Review helpfulness voting, photo/video attachments, or seller responses — none of the “advanced” review-platform features exist; this is the bare-basics submit → moderate → display loop only.
- Spam/abuse filtering beyond manual moderation — every review requires a human Publish/Reject decision; there is no automated content-moderation heuristic.
Open questions
- No dedicated test file exists for this subsystem in
apps/reference-storefront/test/(packages/reviewsitself is unit-tested, but the app-layer wiring — PDP display, the submission action, the moderation queue’s product-name resolution — is currently only live-verified against a running dev server, not covered by an automated regression test). A real, disclosed gap, not forgotten — worth a dedicatedreviews.test.tsas a follow-up.