Skip to content

feat(comment): comments, ratings and reviews — one primitive, attachable to anything - #200

Merged
WebTigers merged 2 commits into
mainfrom
feat/comments
Aug 29, 2026
Merged

feat(comment): comments, ratings and reviews — one primitive, attachable to anything#200
WebTigers merged 2 commits into
mainfrom
feat/comments

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

Builds COMMENTS.md. The last WP-parity platform gap, as a core module that is off by default.

A review IS a comment with a rating

One comment table, nullable rating — null = a plain comment, 1–5 = a review, parent_id set = a reply. One moderation queue, one spam path, one admin screen, one /api service.

What ships

Piece Role
Tiger_Comment the subject registry + policy gate
Tiger_Model_Comment / _CommentAggregate the store + the denormalized rollup (0045/0046)
Tiger_View_Helper_Stars half-star display of averages, accessible
modules/comment /api service, moderation queue, 4 shortcodes, reader JS, 6 locales

Attaches to anything, without core learning what anything is

A provider declares how to resolve a subject to title/URL/exists, which ACL resource gates reading the thread (the subject's own, so a thread can never expose content the caller can't see), whether stars apply, reply depth, and optionally an entitlement gate + ownership check. Core ships page and blog.post; everything else opts in.

Fail-soft throughout: a resolver that throws degrades to "gone" so the moderation queue still renders the orphaned row — precisely when an operator needs to see it — and an entitlement check that errors can never mint a verified badge.

Policy that makes it usable

Pending and spam rows are excluded from the average, so a spammer can't move a score merely by posting. The rollup recomputes inside the same transaction as the write, so a card can never quote a number the thread doesn't support. Plus: hold-then-approve by default, per-user and per-IP rate limits, a honeypot and a time-trap, one rating per person per subject, and no reviewing your own listing.

Settled while building (COMMENTS.md §11)

  • Edit window is bounded (15 min default). Unbounded lets a 1-star review be quietly rewritten after a refund — that turns the rating into a negotiation.
  • An edited body re-enters moderation; a changed rating does not. Otherwise "post something innocuous, get approved, rewrite it" is an open door, while a number bounded 1–5 has nothing to moderate.
  • No guest ratings. Guest commenting stays a config opt-in; an anonymous score is an open ballot box.

Tests

31 new. Three real bugs they caught:

  1. softDelete() takes a WHERE clause, not an id — deletes were silent no-ops.
  2. The grouped aggregates were built on activeSelect(), which omits the FROM part by default.
  3. One that only appeared in the full suite: merging onto a bootstrap-published read-only Zend_Config throws. Isolation-passing / suite-failing is exactly the flake worth killing at the source.

Full suite: OK (2142 tests, 21551 assertions), zero deprecations.

…ble to anything

Builds COMMENTS.md. The last WP-parity platform gap, shipped as a core module that is OFF by
default (tiger.comment.enabled) — an open comment endpoint is the most-attacked surface a CMS has,
and it carries a standing moderation duty a brochure site never asked for.

A review IS a comment with a rating. One `comment` table with a nullable `rating`: null = a plain
comment, 1-5 = a review, parent_id set = a reply. One moderation queue, one spam path, one admin
screen, one /api service.

- Tiger_Comment — the subject registry. A module declares how to resolve a subject to a
  title/URL/exists, which ACL resource gates READING the thread (the SUBJECT's own, so a thread can
  never expose content the caller can't see), whether stars apply, reply depth, and optionally an
  entitlement gate + an ownership check. Core ships providers for `page` and `blog.post` only;
  everything else opts in. Fail-soft throughout: a resolver that throws degrades to "gone" so the
  moderation queue still renders the orphaned row, and an entitlement check that errors can never
  mint a verified badge.
- Tiger_Model_Comment + Tiger_Model_CommentAggregate (0045/0046). The rollup is recomputed inside
  the same transaction as the write, so a card can never quote a number the thread doesn't support.
  Pending and spam rows are excluded from the average — otherwise a spammer moves a score merely by
  posting, before anyone moderates.
- Tiger_View_Helper_Stars — half-star DISPLAY of averages (whole-star input), role="img" with a real
  aria-label and the numeric value as text. A row of glyphs is not an accessible rating.
- modules/comment — the /api service (where nearly all the policy lives: feature gate, subject ACL,
  self-review refusal, rate limits, honeypot + time-trap, moderation posture), the moderation queue,
  four shortcodes, the reader JS, and six locales.

Settled while building, recorded in COMMENTS.md §11: the edit window is BOUNDED (15 min default) —
unbounded lets a 1-star review be quietly rewritten after a refund; an edited body re-enters
moderation but a changed rating does not; no guest ratings (an anonymous score is an open ballot
box) though guest commenting stays a config opt-in.

31 tests. Two real bugs they caught: softDelete() takes a WHERE clause, not an id, so deletes were
silently no-ops; and the grouped aggregates were built on activeSelect(), which omits the FROM part
by default. A third only appeared in the FULL suite — merging onto a bootstrap-published read-only
Zend_Config throws, so the test now merges onto a modifiable copy.

Full suite: OK (2142 tests, 21551 assertions), zero deprecations.
…lup read

The coverage gate caught the gap honestly — 71.5% against a 72% floor — so this closes it with
tests rather than by lowering the ratchet.

19 more: the star helper (including the accessibility contract, which is the part that would rot
silently), the shortcode renderers, core's own page/blog subject resolvers, and the batch rollup
read that is the entire reason comment_aggregate exists.

One of them found a real bug rather than just adding coverage: Comment_Service_Render fell back to
a bare Zend_View outside a themed request, and a bare view does not know Tiger's helpers — so
$this->stars() threw "Plugin by name 'Stars' was not found" anywhere the renderer ran without a
booted front controller (a CLI render, a queued job, a test). It now registers the helper path
explicitly instead of assuming the bootstrap ran.

Full suite: OK (2161 tests, 21594 assertions).
@WebTigers
WebTigers merged commit aaccc10 into main Aug 29, 2026
13 checks passed
@WebTigers
WebTigers deleted the feat/comments branch August 29, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant