Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,6 @@ launch gate is the no-shell web installer; these ship continuously, whenever a u
actions (password set/reset, lock/unlock); a general **options registry** (declared keys → `config` UI,
per config-discipline) that **masks secrets** (`mail.smtp.password` et al. never rendered — consider a
`secret` flag on the `config` table and/or at-rest encryption). *(Membership/invite UX stays app-side.)*
- **Comments, ratings & reviews — the last WP-parity gap** *(design of record:
[COMMENTS.md](COMMENTS.md)).* One core module, **off by default**, where **a review IS a comment
with a rating** — one `comment` table with a nullable `rating`, never a separate review store.
Attaches to **anything** through a subject-provider registry (`Tiger_Comment::registerSubject`,
modeled on `Tiger_Search`/`Tiger_Audience`): a CMS page, a blog article, a marketplace listing, a
shop product. 5 stars with **half-star display** of averages (whole-star input). Denormalized
`comment_aggregate` because a 60-card grid cannot average N rows per card. The differentiator is
the **verified reviewer** — Tiger has an entitlement oracle, so "every review is from someone who
bought it" is a claim WordPress structurally cannot make. Fills the rating/download overlay a
marketplace already publishes (`TigerMarketplace/docs/design/reputation.md` §8).

- **SMS OTP channel** — email OTP ships; add a `Tiger_Sms` transport (a `Tiger_Mail` sibling; SNS/Twilio,
creds in DB config) + `requestLoginCodeSms`/`verifyLoginCodeSms` reusing the channel-agnostic
`_completeCodeLogin` (`sms_otp`). Substrate built (`auth_challenge` + the `sms` credential factor).
Expand Down
10 changes: 9 additions & 1 deletion CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> before assuming something isn't built. `@api` = stable to build on; `@internal` = may change.
> Grouped by **capability** (across layers), not by directory.

**195 classes** across **32 capabilities** · **19 modules**. Full prose: [FEATURES.md](FEATURES.md) (what) · [ARCHITECTURE.md](ARCHITECTURE.md) (why). Not-yet-built: [BACKLOG.md](BACKLOG.md).
**199 classes** across **33 capabilities** · **20 modules**. Full prose: [FEATURES.md](FEATURES.md) (what) · [ARCHITECTURE.md](ARCHITECTURE.md) (why). Not-yet-built: [BACKLOG.md](BACKLOG.md).

## Capabilities (`library/Tiger`)

Expand Down Expand Up @@ -275,6 +275,7 @@
- **Tiger_View_Helper_PageField** `@api` — read a custom field value (Tiger_Fields) from a page on the front end. · `library/Tiger/View/Helper/PageField.php`
- **Tiger_View_Helper_PageScript** `@api` — register a page-specific JS file from a view WITHOUT a `<script>` tag. · `library/Tiger/View/Helper/PageScript.php`
- **Tiger_View_Helper_PageStyle** `@api` — register a page-specific stylesheet from a view WITHOUT a `<style>` tag. · `library/Tiger/View/Helper/PageStyle.php`
- **Tiger_View_Helper_Stars** `@api` — render a 1-5 star rating with HALF-STAR precision. · `library/Tiger/View/Helper/Stars.php`
- **Tiger_View_Helper_T** `@api` — translate a semantic key inside a view: `$this->t('cms.page.title')`. · `library/Tiger/View/Helper/T.php`

### Routing & controllers
Expand All @@ -295,11 +296,17 @@
- **Tiger_Model_AgentConversation** `@api` — a TigerAgent chat thread (see migration 0034). · `library/Tiger/Model/AgentConversation.php`
- **Tiger_Model_AgentMessage** `@api` — one message in an agent conversation (see migration 0035). · `library/Tiger/Model/AgentMessage.php`
- **Tiger_Model_AgentRun** `@api` — one turn's execution + control record (see migration 0036). · `library/Tiger/Model/AgentRun.php`
- **Tiger_Model_Comment** `@api` — the one comment/review store (migration 0045). · `library/Tiger/Model/Comment.php`
- **Tiger_Model_CommentAggregate** `@api` — the denormalized per-subject rollup (migration 0046). · `library/Tiger/Model/CommentAggregate.php`
- **Tiger_Model_Menu** `@api` — Menu — custom navigation menus (see migration 0017). · `library/Tiger/Model/Menu.php`
- **Tiger_Model_Session** `@api` — Session — gateway for the DB session store (see migration 0010). · `library/Tiger/Model/Session.php`
- **Tiger_Model_Table** `@api` — Base table-gateway for Tiger models. · `library/Tiger/Model/Table.php`
- **Tiger_Uuid** `@api` — UUID generation for Tiger primary keys. · `library/Tiger/Uuid.php`

### Other (unmapped — add to $CAPS)

- **Tiger_Comment** `@api` — the subject registry and policy gate for comments, ratings and reviews. · `library/Tiger/Comment.php`

## Modules (`modules/*` — activatable features)

- **Access** (`access`, plugin) · services: Org, User · `modules/access`
Expand All @@ -310,6 +317,7 @@
- **Blog** (`blog`, app) · services: Post, Taxonomy · `modules/blog`
- **CMS** (`cms`, app) · services: Menu, Page, Settings · `modules/cms`
- **Code** (`code`, developer) · services: Code · `modules/code`
- **Comments** (`comment`, app) · services: Comment, Render, Subjects · `modules/comment`
- **Identity** (`identity`, plugin) · services: Identity · `modules/identity`
- **Mcp** (`mcp`, module) · services: Settings · `modules/mcp`
- **Media** (`media`, plugin) · services: Media, Settings · `modules/media`
Expand Down
39 changes: 26 additions & 13 deletions COMMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ the platform *why* read [ARCHITECTURE.md](ARCHITECTURE.md); for the admin-screen
[ADMIN.md](ADMIN.md); for the `/api` contract read [WEBSERVICES.md](WEBSERVICES.md); for how a
marketplace consumes the aggregate read `TigerMarketplace/docs/design/reputation.md`.

> **Status: design of record — NOT built.** This records the decisions and their rationale so we
> don't relitigate them or drift when the code lands. Everything below is target behaviour.
> **Status: BUILT (2026-08-29), off by default.** Ships `Tiger_Comment` (the subject registry),
> `Tiger_Model_Comment` + `Tiger_Model_CommentAggregate` (migrations 0045/0046), the
> `Tiger_View_Helper_Stars` half-star renderer, and `modules/comment` (the `/api` service, the
> moderation queue, the four shortcodes, the reader JS, six locales). Not yet built: the spam-check
> registry's first real implementation, reply notifications, and the marketplace subject provider
> (§8) — all §10 step 6.

---

Expand Down Expand Up @@ -222,17 +226,26 @@ Nothing about that contract changes. This module fills numbers that are currentl

---

## 11. Open questions

- **Editing window.** Let an author edit their own comment forever, or for N minutes? (Forever is
friendlier; it also lets a 1-star review be quietly rewritten after a refund — a real pattern.)
- **Does an edited review re-enter moderation?** Leaning yes for the body, no for the rating.
- **Threading depth** for plain comments — flat, one level, or N? (Registry field exists; the
default is the question.)
- **Do we surface `comment_count` separately from `rating_count`?** They differ whenever ratings are
optional, and conflating them overstates engagement.
- **Guest ratings.** Sign-in-required is the default; is a guest *rating* (no body) ever acceptable,
or is that just an open ballot box?
## 11. Settled during the build

- **Editing window: bounded, not forever** — `tiger.comment.edit_window`, default 15 minutes. The
deciding case was the one named in the original question: an unbounded window lets a 1-star review
be quietly rewritten after a refund, which turns the rating into a negotiation.
- **An edited BODY re-enters moderation** when the install holds comments; 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.
- **Threading defaults to 1** (one reply level), per subject via the registry's `threading`. A reply
must belong to the same subject as its parent, so a thread can't be grafted onto another.
- **`comment_count` and `rating_count` stay separate**, in the table and in the payload.
- **No guest ratings.** Guest *commenting* is a config opt-in (`tiger.comment.allow_guests`, off);
ratings still require an identity, because an anonymous score is just an open ballot box.

Still open:

- **A pending rating is excluded from the average** (so posting alone can't move a score) — which
means a busy subject's public average lags moderation. Acceptable, but worth revisiting if a queue
ever backs up.
- The **spam-check registry** exists as a design; nothing registers into it yet.

---

Expand Down
25 changes: 25 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,31 @@ framework.
from order + depth). Each chip opens a scrollable properties modal; everything persists over
`/api` (insert on drop, reorder on move, update on save, with a success toast).

## Comments, ratings & reviews

- **One primitive for both.** A review *is* a comment with a rating — one `comment` store with a
nullable `rating`, so there is one moderation queue, one spam path and one admin screen rather
than a parallel "reviews" feature.
- **Attaches to anything.** A module registers a subject provider (`Tiger_Comment::registerSubject`)
and a thread can hang off a CMS page, a blog article, a shop product, a marketplace listing — core
never learns what those are. The provider supplies the title/link, the ACL resource that gates who
may read the thread, whether stars apply at all, and the reply depth.
- **5 stars, half-star display.** Input is whole stars; averages render to the nearest half
(`$this->stars(4.3)`), with the numeric value as text and a real `aria-label` — a row of glyphs
alone is not an accessible rating.
- **Verified reviewers.** A provider can declare an entitlement check, so a review from someone who
actually bought the thing is badged — the licence authority, an order, or a membership grant
answers it.
- **Moderation built in.** Hold-then-approve by default, a queue in the admin, 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. Pending comments never move a public average.
- **Denormalized rollups** (`comment_aggregate`) so a grid of cards reads one row each instead of
averaging N comments per card.
- **Off by default** (`tiger.comment.enabled`). An open comment endpoint is the most-attacked
surface a CMS has; turning it on is a deliberate act.
- **Shortcodes + helpers** — `[comments]` / `[reviews]`, `[stars]`, `[rating_summary]`, plus
`$this->stars()` for a theme that would rather not go through a shortcode.

## Mail

- **`Tiger_Mail`** — a fluent `Zend_Mail` wrapper. The transport is config-driven: boring PHP
Expand Down
Loading
Loading