Comments — Improvement & Growth Plan
Package: capell-app/comments · Kind: package · Tier: premium · Product group: Capell Engagement · Bundle: comments · Status: Draft
1. Snapshot
Section titled “1. Snapshot”Comments adds moderated, threaded, dynamically-loaded discussion to registered Capell content (pages and, when Blog is installed, articles). It exposes two surfaces: an admin moderation experience (CommentModerationInbox page, CommentResource + CommentAuthorResource, CommentStatsWidget, LatestCommentsWidget) and a frontend Livewire component (CommentThreadComponent) loaded post-page via a no-store endpoint (RenderCommentThreadController → route capell-comments.thread). Key Actions are CreateCommentAction, BuildPublicThreadAction / ResolvePublicCommentableThreadAction, TransitionCommentStatusAction, ToggleCommentReactionAction, RequestCommentEmailVerificationAction / VerifyCommentAuthorEmailAction, RequestCommentReplyNotificationAction, and RegisterDefaultCommentablesAction. Models/tables: comments, comment_authors, comment_tokens, comment_moderation_events, comment_reactions (all registered as protected tables, all soft-delete on comments). Deps: capell-app/{core,admin,frontend}, Filament, Livewire, lorisleiva/laravel-actions, spatie/laravel-data, spatie/laravel-settings. Author name/email are encrypted casts; email is also stored as an HMAC email_hash for lookup.
Current marketplace summary: “Add moderated, threaded discussion to any Capell page or article — with cache-safe public rendering, encrypted author records, and per-site moderation controls, no custom code required.” Marketplace media declared in the manifest: 5 committed assets: the extension card plus four route-backed PNG captures for the same surfaces listed in docs/screenshots.json (moderation inbox, comments resource, authors resource, public thread). The committed runtime captures were generated from a Capell screenshot-runner fixture with real Filament admin chrome and seeded public thread data, and the screenshot contract requires the current capell-app/block-library package name instead of the stale capell-app/content-blocks name.
Completed Improvement Slices
Section titled “Completed Improvement Slices”- 2026-06-03: Rewrote marketplace/Composer copy, declared
LatestCommentsWidgetin manifest contributions, and added manifest coverage for registered dashboard widgets. - 2026-06-04: Added public comment form bot-trap controls: a hidden honeypot field, configurable minimum form age, action-level rejection before persistence, component state reset, and action/Livewire tests.
- 2026-06-04: Implemented real
CommentsHealthCheckdiagnostics for required storage tables, settings registration, the public thread route, and the public thread Livewire component, with focused failure-mode tests. - 2026-06-04: Implemented automatic local spam scoring for configured link-count and blocked-term rules, storing
spam_reasons, marking flagged submissions asSpam, and skipping verification tokens for auto-spam comments. - 2026-06-04: Hardened the public submit throttle key to use commentable + IP data without attacker-controlled author email, with Livewire regression coverage.
- 2026-06-04: Wired queued moderator notifications for configured moderator email addresses when new comments enter pending approval or pending email verification, with listener registration and status-gating tests.
- 2026-06-04: Added auto-inject anonymous-leakage coverage for the real render-hook path plus package Arch tests for strict equality, public-runtime admin/authoring isolation, and public Blade database/authoring-marker guards.
- 2026-06-04: Implemented settings-aware reply pagination with per-parent “load more replies” support, bounded child hydration, recursive public rendering, and Livewire-safe public comment DTO serialization.
- 2026-06-04: Hardened public body sanitization by stripping invisible Unicode format controls and ASCII control bytes, and broadened spam link detection to count scheme,
www., and bare-domain links without counting email domains. - 2026-06-04: Memoized the resolved public commentable model within each Livewire request, avoiding duplicate page lookups during submit/refresh flows without serializing Eloquent models into Livewire state.
- 2026-06-04: Locale-pinned public timestamp labels to the resolved commentable language so
diffForHumans()output follows pagelanguage_idinstead of the ambient app locale, with Livewire serialization coverage. - 2026-06-04: Added enforceable performance-budget coverage for public thread hydration/rendering and admin comment widgets, and batched sibling reply counts to avoid empty-grandchild query fanout.
- 2026-06-04: Added privacy retention and erasure tooling for old visitor hashes, moderation notes, tokens, and author PII, plus hash-secret rotation documentation.
- 2026-06-04: Added the pluggable
CommentSpamProvidercontract, default local provider, configured provider chain, and spam-check context data for Akismet/Turnstile-style adapters. - 2026-06-04: Added public Like reactions with aggregate public counts, plus approved-reply notifications with tokenized parent-author opt-out handling.
- 2026-06-04: Added committed illustrated marketplace gallery assets for all four required screenshot-contract surfaces, with manifest coverage to keep those gallery assets present.
- 2026-06-06: Replaced the illustrated Comments marketplace previews with route-backed PNG captures from the Capell screenshot runner for the moderation inbox, comments resource, author resource, and public thread.
2. Improvements (existing functionality)
Section titled “2. Improvements (existing functionality)”-
Shipped 2026-06-04: Wire moderator new-comment notifications —
CommentCreatednow has a queuedNotifyModeratorsOfNewCommentlistener registered byCommentsServiceProvider. It notifies valid, de-duplicated addresses fromconfig('capell-comments.notifications.moderators')when a comment lands inPendingApprovalorPendingEmailVerification, and skips spam/approved comments. —src/Events/CommentCreated.php,src/Listeners/NotifyModeratorsOfNewComment.php,src/Notifications/ModerateCommentNotification.php,src/Providers/CommentsServiceProvider.php,tests/Feature/ModeratorNotificationTest.php— M -
Shipped 2026-06-04: Make
CommentsHealthChecka real check —CommentsHealthCheck::runDiagnostics()now reports storage-table, settings-registration, thread-route, and Livewire-component checks, andpassed()reflects the aggregate result. Focused tests cover happy path plus missing table, settings, and route failures. —src/Health/CommentsHealthCheck.php,tests/Feature/CommentsHealthCheckTest.php— M -
Shipped 2026-06-04: Add a honeypot + minimum-render-age check to the public form —
thread.blade.phpnow renders a hidden honeypot field,CommentThreadComponenttracksformRenderedAt, andCreateCommentActionrejects honeypot-filled or too-fast submissions before persistence. The minimum age is configurable atcapell-comments.spam.minimum_form_age_seconds. —src/Livewire/CommentThreadComponent.php,resources/views/livewire/thread.blade.php,src/Data/CreateCommentData.php— S -
Shipped 2026-06-04: Paginate / lazy-load replies —
BuildPublicThreadActionnow resolves root and reply page sizes throughCommentSettingsResolver, hydrates each parent with a bounded child page, exposesreplyCount/hasMoreReplies, and accepts per-public-id reply limits.CommentThreadComponenttracks per-parent reply limits and exposesloadMoreReplies(), while the public Blade now renders nested comments recursively with a translated load-more action. —src/Actions/BuildPublicThreadAction.php,src/Livewire/CommentThreadComponent.php,src/Data/PublicCommentData.php,resources/views/livewire/partials/comment-list.blade.php,tests/Integration/Actions/PublicThreadActionTest.php,tests/Integration/CommentThreadComponentTest.php— M -
Shipped 2026-06-04: Surface validation errors per-field on submit —
CommentThreadComponent::submit()now resets stale submit/error state, catchesValidationExceptionfrom rate-limit andCreateCommentActionvalidation, and maps each message into Livewire’s component error bag so the existing per-field@errorblocks bind reliably. Focused Livewire coverage proves guest name, email, and body validation messages surface on the matching fields and stale success/errors are replaced by the current submit result. —src/Livewire/CommentThreadComponent.php,tests/Integration/CommentThreadComponentTest.php— S -
Shipped 2026-06-04: Stop double-querying the commentable on every Livewire action —
CommentThreadComponentnow keeps the decrypted/resolved model in private request-local properties and resets that cache on Livewire hydration. Submit flows reuse the same model forCreateCommentActionand the post-submitrefreshComments()call, with query-count coverage proving the duplicate page lookup is gone. —src/Livewire/CommentThreadComponent.php,tests/Integration/CommentThreadComponentTest.php— S -
Shipped 2026-06-04: Enforce public/admin performance budgets —
BuildPublicThreadActionnow batches approved child-count lookups for each sibling group and skips empty child fetches, removing the old per-comment empty-grandchild query fanout.PerformanceBudgetTestseeds a bounded hot public thread, asserts hydration stays at or below 20 queries, and checks warmed Blade rendering againstcapell.jsonfrontendRenderBudgetMs: 20;CommentsAdminSurfaceTestasserts the stats/latest-comments widgets stay underadminQueryBudget: 40. —src/Actions/BuildPublicThreadAction.php,tests/Integration/PerformanceBudgetTest.php,tests/Integration/Filament/CommentsAdminSurfaceTest.php— M -
Shipped 2026-06-03: Index/labels parity in admin —
LatestCommentsWidgetis registered at runtime and declared incapell.jsoncontributes[], with manifest coverage proving every dashboard widget stays discoverable by marketplace and Diagnostics tooling. —capell.json,tests/Unit/ManifestRequirementsTest.php— S
3. Missing Features (gaps)
Section titled “3. Missing Features (gaps)”Manifest capabilities[] = comments, comments-admin, comments-frontend, comments-created-event — these are structurally present. The gaps below are category table-stakes that are advertised in config/README but not built, plus differentiators.
-
Shipped 2026-06-04: Spam scoring (table-stakes, advertised).
ScoreCommentSpamActionreadsconfig('capell-comments.spam.max_links')andspam.blocked_terms, returnsCommentSpamScoreData, andCreateCommentActionstoresspam_reasons, setsCommentStatus::Spam, stampsmarked_spam_at, and skips verification-token creation for flagged submissions. Focused coverage proves direct scoring and create-flow persistence. —src/Actions/ScoreCommentSpamAction.php,src/Data/CommentSpamScoreData.php,src/Actions/CreateCommentAction.php,tests/Integration/Actions/CreateCommentActionTest.php -
Shipped 2026-06-04: Rate-limit hardening (anti-abuse). The primary public submit throttle key now uses commentable type, commentable ID, and requester IP only, so changing
$this->authorEmailcannot reset the primary bucket. Focused Livewire coverage proves a second submission to the same thread/IP is throttled even when the email changes. —src/Livewire/CommentThreadComponent.php,tests/Integration/CommentThreadComponentTest.php -
Future upsell: moderator digests / richer templates. Instant configured-address moderator notifications are shipped. A daily moderation digest, role-based moderator discovery, and Email Studio-backed templates remain natural premium differentiators outside the current package roadmap;
supports: capell-app/email-studiois the intended vehicle. -
Shipped 2026-06-04: Reactions / voting (differentiator).
comment_reactionsstores Like reactions keyed by authenticated user or hashed visitor request data.ToggleCommentReactionActiononly accepts approved comments on the current commentable, andPublicCommentDataexposes aggregatereactionCountwithout leaking actor identities. -
Shipped 2026-06-04: Author reply notifications (differentiator).
RequestCommentReplyNotificationActionruns when a reply becomes approved via trusted-author auto-publish or moderation transition, sends a queued parent-author email only for verified non-blocked authors, and creates aReplyNotificationOptOuttoken consumed byDisableCommentAuthorReplyNotificationsAction. -
Future upsell: edit / delete window for authors. Authenticated/verified authors cannot edit or soft-delete their own comments from the frontend; only admins transition status. A short author edit window is useful product depth, but it is intentionally outside the completed moderation/cache-safety roadmap.
-
Shipped 2026-06-04: Akismet / external spam provider hook (differentiator).
CommentSpamProviderlets host apps append Akismet, Turnstile, CAPTCHA, or reputation-service adapters throughcapell-comments.spam.providers.ConfiguredCommentSpamProvidercombines provider scores,LocalCommentSpamProviderpreserves built-in link/blocked-term checks, andCreateCommentActionpasses sanitized body, site/commentable context, public author fields, IP/user-agent, and authenticated user context into the scorer.
4. Issues / Risks
Section titled “4. Issues / Risks”-
Health check coverage shipped.
src/Health/CommentsHealthCheck.phpnow exposes real Diagnostics results for the critical package-health declaration incapell.json. Residual risk: these checks cover package wiring, not spam scoring, notifications, or public render budgets. -
Local spam scoring shipped.
spam.max_links,spam.blocked_terms, thespam_reasonscolumn, andlinkCount()are now enforced duringCreateCommentAction. Link detection now covershttp(s)://,www., and bare-domain links while avoiding email-domain false positives. Residual risk: the heuristic remains intentionally local and simple; external spam providers, CAPTCHA/Turnstile, and richer reputation scoring remain separate roadmap items. -
Throttle bypass via email field shipped. The public submit throttle no longer includes attacker-controlled author email in the primary bucket. Residual risk: there is not yet a secondary per-author-email cap or broader abuse telemetry.
-
Performance budgets shipped. Manifest budgets are now enforced by focused tests: a seeded public thread checks bounded hydration query count plus warmed Blade render time against
frontendRenderBudgetMs: 20, and admin stats/latest-comments widget queries are checked againstadminQueryBudget: 40. Residual risk: these are deterministic package-level guards, not production load tests across every host theme. -
XSS / sanitization hardening shipped. Body is
strip_tags+ whitespace-collapsed, capped at 5000 chars, and now strips Unicode format controls plus ASCII control bytes before storage (src/Support/CommentBodySanitizer.php). Output is Blade-escaped via{{ }}andPublicCommentDataomits sensitive fields, so stored-XSS and text-spoofing surface is low. Focused coverage proves bidi/zero-width stripping and broader link counting. -
Public-output safety coverage shipped.
BuildPublicThreadAction::toData()correctly emits only public id, sanitized body, display name, timestamp, depth, reply count, children — no status, model id, email, hashes, tokens, or admin URL (matches the README Public Safety contract). The thread endpoint setsCache-Control: no-store, private(src/Http/Controllers/RenderCommentThreadController.php:20).AutoInjectRenderHookTestnow exercises the realRenderHookRegistryauto-inject path and proves the cached shell omits comment bodies, author PII, model identifiers, moderation state, Livewire snapshots, and admin URLs.CommentsBoundaryTestadds package Arch coverage plus public Blade guards against database access and authoring markers. -
PII / retention shipped. Author
name/emailare encrypted at rest andemail_hashis HMAC.capell-comments:privacy-retentionnow deletes old/consumed tokens, clears stale visitor hashes and moderation notes, and can anonymize matching author records by email while preserving public comment bodies and thread structure.docs/privacy-and-retention.mddocuments package hash secrets,app.keyfallback risk, rotation expectations, dry-run usage, and subject erasure behavior. Residual risk: plaintextinternal_notes/moderation_noteremain private admin fields until retention or erasure clears them; they are not encrypted separately. -
Test gaps. Coverage now includes health diagnostics, spam scoring, sanitizer hardening, moderator notification wiring/status-gating,
auto_injectanonymous-leakage, reply pagination, performance budgets, privacy retention/erasure, public-output Architecture guards, Livewire commentable memoization, locale-pinned public timestamps, public submit throttling, and bot-trap rejection at action and Livewire levels. -
i18n shipped. Strings are translated via
capell-comments::namespaces ✔. Public timestamp labels are now produced byBuildPublicThreadActionwith the commentable language locale, serialized throughPublicCommentData, and rendered as preformatted labels in Blade so Livewire hydration does not fall back to the ambient app locale. -
Done/Shipped: manifest pairing matches README. README “Best Used With” lists Blog, Email Studio, and HTML Cache;
capell.jsonnow declares all three underdependencies.supports.
5. Marketplace & Selling
Section titled “5. Marketplace & Selling”Critique. The marketplace summary and the composer description are now more specific about cache-safe public rendering, encrypted author records, and per-site moderation controls. The marketplace gallery now uses committed route-backed PNG captures for the four high-value screenshot-contract surfaces in docs/screenshots.json: the moderation inbox, comments resource, author resource, and public thread.
Improved 1-sentence summary: “Add moderated, threaded discussion to any Capell page or article — with cache-safe public rendering, encrypted author records, and per-site moderation controls, no custom code required.”
Improved 3–4 sentence description: “Comments lets site owners open public, threaded discussion on pages and Blog articles while editors keep full control from a dedicated moderation inbox with author records, status transitions, and dashboard widgets. Threads load after the page via a private, no-store endpoint, so they never contaminate cached HTML or leak moderation state to anonymous visitors. Author identities are encrypted at rest and matched by HMAC, with optional email verification, guest-vs-authenticated identity modes, and configurable approval, throttling, and depth limits per site or content type. Developers register a commentable type once and the package resolves public-safe thread data for the frontend.”
Future media upsell: a short GIF of the moderation approve/reject flow and a before/after of a cached page with comments loading in would improve the listing, but the required marketplace screenshot contract is complete. The comments package contract is runner-readable, the marketplace gallery points at committed PNG captures, and its dependency list uses the current capell-app/block-library package name.
Pricing / tier / bundle positioning: tier: premium, bundle: comments, proposedLicense: paid, requestedCertification: first-party, supportPolicy: priority. The package now has the baseline premium moderation loop: spam scoring plus instant configured moderator notifications. Cross-sell paths already in deps/manifest: Blog (supports) for article discussion — lead with this in the listing; Email Studio (supports) for richer moderator/author notification templates and digests; HTML Cache (README “Best Used With”) — position the no-store design as the reason these two coexist safely. Extension-suite angle: package with Blog + Email Studio as an “Engagement Suite.”
Differentiators / value props: cache-safe post-load rendering; encrypted PII + HMAC dedup; per-site & per-commentable-type setting overrides; verification flows (verify-then-moderate / moderate-then-verify); soft-delete + full moderation audit trail (comment_moderation_events). Target buyer: content/marketing teams on Capell (esp. Blog users) who want managed discussion without standing up Disqus/a third party and without breaking page caching.
Keywords/tags (8–12): comments, discussion, threaded-comments, moderation, engagement, blog-comments, spam-filtering, email-verification, livewire, cache-safe, filament, capell.
6. Prioritized Roadmap
Section titled “6. Prioritized Roadmap”| Item | Bucket | Effort | Impact | Section ref |
|---|---|---|---|---|
Shipped 2026-06-04: implement real CommentsHealthCheck (tables, settings, route, component) | Done | M | High | §2, §4 |
Shipped 2026-06-04: implement automatic spam scoring (max_links, blocked_terms, write spam_reasons) | Done | M | High | §3, §4 |
| Shipped 2026-06-04: fix throttle key (drop attacker-controlled email from primary bucket) | Done | S | High | §3, §4 |
Shipped 2026-06-04: wire moderator new-comment notification listener on CommentCreated | Done | M | High | §2, §3 |
Shipped 2026-06-04: add auto_inject anonymous-leakage tests and Arch tests | Done | M | High | §4 |
Shipped 2026-06-03: add LatestCommentsWidget to capell.json contributes[] | Done | S | Med | §2, §4 |
Shipped 2026-06-06: generate/wire the 4 runtime PNG captures from docs/screenshots.json; marketplace gallery now promotes Capell runner PNGs for the moderation inbox, comments resource, author resource, and public thread | Done | S | Med | §5 |
Shipped 2026-06-04: implement reply pagination using reply_page_size; cap subtree query | Done | M | High | §2, §3 |
Shipped 2026-06-04: benchmark + assert frontendRenderBudgetMs/adminQueryBudget | Done | M | Med | §4 |
| Shipped 2026-06-04: harden sanitizer (bidi/zero-width strip; broaden link detection) | Done | S | Med | §4 |
| Shipped 2026-06-04: memoize resolved commentable in Livewire component | Done | S | Low | §2 |
Shipped 2026-06-04: locale-pin public timestamps to page language_id | Done | S | Low | §4 |
| Shipped 2026-06-04: pluggable external spam provider (Akismet/Turnstile) contract | Done | M | Med | §3 |
| Shipped 2026-06-04: reactions/voting + author-reply notifications (Engagement Suite up-sell) | Done | L | Med | §3, §5 |
| Shipped 2026-06-04: PII retention/erasure command + secret-rotation docs for hashes | Done | M | Med | §4 |