feat(comments): reactions + Wilson 'Best' sort (Tier 1)#1540
feat(comments): reactions + Wilson 'Best' sort (Tier 1)#1540marcusbellamyshaw-cell wants to merge 3 commits into
Conversation
🦋 Changeset detectedLatest commit: 9e370e2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Scope checkThis PR changes 1,012 lines across 16 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
c3c773f to
69de904
Compare
Tier 1 of the best-in-class comments RFC. Visitors can react (like) to approved comments; reactions are stored first-party in a new _emdash_comment_reactions table, deduped per voter by a salted IP hash, and served via a public, honeypot- and rate-limited endpoint at GET/POST /_emdash/api/comments/:collection/:contentId/reactions. The <Comments> component gains two opt-in props: `reactions` (render a like button + live counts) and `sort="best"` (Reddit-style Wilson lower-bound ranking). Posting is progressively enhanced via a tiny inline script emitted only when reactions are enabled. Additive and backward-compatible: new table, new route, new optional props with behavior-preserving defaults. Includes a changeset and tests. AI disclosure: implemented with assistance from Claude (claude-opus-4-8); all code verified against the repository and the full test suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gration The reactions endpoint (GET/POST /_emdash/api/comments/:collection/:contentId/reactions) needs an explicit injectRoute entry in injectCoreRoutes — without it the path falls through to the page handler. Verified end-to-end against the demo: toggle on/off and aggregate counts return the expected JSON. AI disclosure: implemented with assistance from Claude (claude-opus-4-8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ucket limit Adversarial self-review follow-ups on the reactions prototype: - toggle() no longer does read-then-write. A concurrent double-toggle from the same voter could both see "no row" and have the second INSERT violate the unique index, surfacing as a 500. Now an idempotent INSERT ... ON CONFLICT DO NOTHING branches on whether a row was written, so it never throws. - handleReactionToggle rejects reactions outside an allowlist (currently just "like", matching the shipped widget) so a caller can't spam arbitrary reaction strings and bloat a comment's count map. - The public reactions route documents that the salted-IP voter hash collapses to a shared "unknown" bucket without a trusted IP (per-visitor dedup is the Tier 2 visitor-identity primitive), mirroring the comment ingest route. Adds handler-level tests (happy path, unsupported reaction, non-approved and missing comment, concurrent-toggle invariant). lint + typecheck clean; 82 comment tests pass. AI disclosure: implemented with assistance from Claude (claude-opus-4-8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69de904 to
9e370e2
Compare
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
What does this PR do?
Adds comment reactions (positive-only "like") and a Wilson score "Best" sort to the
<Comments>component. This is Tier 1 of the best-in-class comments RFC, approved by @ascorbic._emdash_comment_reactionstable (migration044) — deduped per voter via a salted IP hash (same privacy primitive as commentip_hash)GET/POST /_emdash/api/comments/:collection/:contentId/reactions<Comments>:reactions(like button + live counts) andsort="best"(Wilson lower-bound ranking for top-level comments, replies stay chronological)reactionsis enabled — pages without reactions ship zero additional JSCloses #1497 (Tier 1)
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain. — N/A: no admin UI strings addedAI-generated code disclosure
Screenshots / test output
Before — comments today (chronological, no reactions):
After —
<Comments reactions sort="best" />:Test output: 118 tests pass, 15 new (Wilson math, toggle/dedupe, counts, rate-limit, best-sort ordering).