All 20 checks with why-it-matters prose, severity, and cross-references to related audits.
Without dynamic profile routing, every user points to the same static page or a 404, which kills the social graph before it forms. No shareable profile URLs means no inbound links, no SEO authority on user-generated content, no deep-linking from emails or notifications, and no way for followers to actually view who they follow. The platform degrades into a feed with no identity layer, and retention collapses because users cannot find or remember each other.
Why this severity: High because the identity layer is foundational — every social feature (follow, mention, notify) depends on routable profiles.
community-social-engagement.profiles-identity.dynamic-profile-routingSee full patternUnvalidated profile update endpoints are a direct path to CWE-20 (Improper Input Validation) and CWE-434 (Unrestricted File Upload), both flagged under OWASP A03. A bio field with no length limit lets attackers stuff multi-megabyte payloads into your database on every request. Usernames without character constraints enable injection into downstream rendering contexts. Unvalidated avatar uploads allow executable content to reach your storage bucket, creating stored XSS or server-side execution vectors.
Why this severity: High because unvalidated file uploads and unbounded text fields create stored injection risks that affect every viewer of the profile, not just the submitter.
community-social-engagement.profiles-identity.profile-edit-capabilitySee full patternProfiles without visibility controls violate GDPR Art. 25 (data protection by design) and CCPA §1798.100 (consumer right to control their data). Under OWASP A01 (Broken Access Control) and CWE-284, any user whose profile is marked private but still returned to strangers has had their consent overridden by a code defect. Beyond compliance, exposed private profiles leak follower graphs, contact hints, and behavioral patterns to scrapers and stalkers — a direct user safety failure.
Why this severity: High because bypassed privacy controls expose user data to unauthorized parties at query time, constituting a broken access control failure with immediate personal safety implications.
community-social-engagement.profiles-identity.privacy-visibility-controlsSee full patternMissing user blocking is a critical safety gap under OWASP A01 (Broken Access Control) and CWE-284. Without a blocking system enforced at the query layer, a harassing user remains visible in every surface — profile, feed, search, messaging — regardless of victim intent. This is not merely a UX problem: platforms have faced regulatory scrutiny and civil liability for enabling sustained contact after victims attempted to block. Enforcement must happen at the database level; client-side filtering leaks data before it is suppressed.
Why this severity: Critical because the absence of query-level block enforcement means harassing users retain full visibility into and interaction capability with victims, creating direct personal safety exposure.
community-social-engagement.profiles-identity.user-blocking-reportingSee full patternPrivate profile pages that emit OpenGraph tags leak user identity to search crawlers and social preview scrapers, violating GDPR Art. 25 (data protection by design). A user who marks their profile private reasonably expects that `og:title`, `og:image`, and `og:description` do not appear in Google's index or in link previews shared by third parties. Simultaneously, public profiles without OpenGraph metadata miss every social share amplification opportunity — both failure modes live in the same `generateMetadata` function.
Why this severity: Medium because the leak is passive — crawlers must visit the page — but the privacy violation is real and the SEO cost of missing metadata is measurable.
community-social-engagement.profiles-identity.profile-seo-metadataSee full patternNon-transactional follow mutations create race conditions (CWE-362) that permanently corrupt follower counts — a user can end up showing 5,000 followers in their count column while the actual `follows` row count is 4,200. Worse, without a unique constraint on `(followerId, followedId)`, concurrent double-taps produce duplicate follow rows that inflate counts and break feed queries. Self-following bypasses are both a data integrity failure and an embarrassing product defect. These are not edge-case concerns: follow endpoints are hammered at scale, making TOCTOU collisions routine.
Why this severity: Critical because non-transactional follow mutations cause irreversible count corruption under concurrent load, and absent unique constraints allow data model violations that cascade into feed and notification logic.
community-social-engagement.follow-graph.follow-mutation-logicSee full patternFetching entire follower lists in a single unbounded query is a time-bomb proportional to your most-followed user. At 10,000 followers, a `findMany` with no `take` limit loads tens of thousands of joined records into memory, blows through Supabase row limits, and produces multi-second response times. This is a performance-efficiency failure under ISO 25010:2011 — the system cannot maintain acceptable response times as data volume grows. Power users are exactly the accounts that drive retention; making their profiles unusably slow is a direct business impact.
Why this severity: High because unbounded follower queries fail visibly at scale, producing timeouts and errors precisely for the high-profile users whose profiles receive the most traffic.
community-social-engagement.follow-graph.connection-list-paginationSee full patternResolving follow status with a separate query per user card is a textbook N+1 problem. A feed page showing 50 user cards fires 50 individual `isFollowing` database calls — each a round-trip that adds 5–20ms of latency in a cloud deployment. At 100ms/page the cumulative cost is 5 seconds of blocked rendering. Under ISO 25010:2011 performance-efficiency, this is a structural scalability defect: response time degrades linearly with the number of items displayed, making any growth in user density directly punishing.
Why this severity: Medium because the performance impact is proportional to page size and grows predictably, but it degrades user experience gradually rather than causing immediate failures.
community-social-engagement.follow-graph.relationship-state-resolutionSee full patternRunning `COUNT(*)` on the follows table on every profile page load is a full aggregate scan whose cost grows with your total follow graph size. At 1 million follow relationships, each profile render triggers a scan of millions of rows just to display two integers. This is a denormalization failure under ISO 25010:2011 performance-efficiency: counts that change infrequently (relative to page load frequency) should never be computed live. Stale or incorrect counts are a secondary concern — ghost followers or inflated numbers damage trust in your platform's data integrity.
Why this severity: Medium because live count aggregation degrades read performance predictably as the follow graph grows, but does not cause data loss or security exposure.
community-social-engagement.follow-graph.connection-counts-integritySee full patternWhen a user marks their profile private, they are expressing a consent boundary — covered under GDPR Art. 25 and CCPA §1798.120. An instant-follow model that ignores the private flag grants content access without approval, defeating the user's explicit privacy setting. Under CWE-284, this is a failed authorization check: the platform makes the access control decision on behalf of the user rather than routing it through the user's own approval queue. The fix requires a `FollowRequest` table to hold pending state across the approval workflow.
Why this severity: Low because the gap only surfaces when private accounts exist and is not a data exfiltration vector, but it directly violates the user's stated privacy preference.
community-social-engagement.follow-graph.friend-request-approvalSee full patternA feed query with no indexes on `(user_id, created_at)` forces a full table scan every time any user loads their home feed. At 100,000 posts, this is a sub-second scan; at 10 million posts, it becomes a multi-second query that degrades under concurrent load exactly when traffic peaks. ISO 25010:2011 performance-efficiency requires that response time remains acceptable as data volume grows — an unindexed feed query fails this by design. The fan-out architecture choice (read vs. write) is a secondary concern, but the missing indexes are an immediate performance defect regardless of architecture.
Why this severity: High because feed query performance degrades directly with post volume, making feed load times a leading indicator of database health as the platform scales.
community-social-engagement.activity-feeds.feed-query-optimizationSee full patternBlock enforcement applied only at the profile level but not the feed query means a blocked user's posts continue appearing in the blocker's feed — the block has the appearance of working while delivering no actual protection. This is OWASP A01 (Broken Access Control) and CWE-284: a declared access restriction not enforced at the data layer. GDPR Art. 25 requires that data minimization be built into the processing itself, not applied as a post-fetch client-side filter. Client-side filtering also leaks the count and shape of blocked content to the browser before suppression.
Why this severity: Critical because feed-level block bypass means harassing content reaches victims regardless of their explicit block action, creating direct user safety exposure and a broken-access-control defect.
community-social-engagement.activity-feeds.block-mute-enforcementSee full patternRendering every feed item to the DOM at once sends scroll performance into the ground once a user accumulates a few hundred posts — main-thread jank on scroll, layout thrash on image loads, and memory spikes on mobile that trigger tab kills. A Load More button solves the data problem but still leaves the old DOM nodes parked in memory, and a naive infinite scroll without windowing just accelerates the collapse. Engagement metrics track directly to feed responsiveness.
Why this severity: Low because the feed still functions — degradation is felt only on long sessions with heavy feeds, not on first load.
community-social-engagement.activity-feeds.infinite-scroll-uxSee full patternA feed schema that only supports a single activity type (`post`) cannot evolve to include likes, comments, follows, or shares without a full schema migration. When type support is added ad-hoc through `if/else` chains in the renderer, each new type adds a branch, tests, and cognitive overhead — the component becomes a maintenance liability under ISO 25010:2011. More concretely, the absence of a `type` discriminator on the Activity model means you cannot query or filter by activity type, which blocks every notification and analytics feature that depends on distinguishing activity kinds.
Why this severity: Medium because the maintainability impact is indirect — code still works for the single type — but adding any second activity type without this structure triggers a disruptive schema migration.
community-social-engagement.activity-feeds.polymorphic-feed-itemsSee full patternA feed endpoint with no `Cache-Control` header or client-side `staleTime` triggers a full database query on every page visit, every back-navigation, and every tab focus. For a social feed, this means re-fetching 20 posts with author joins and like counts every few seconds per active user — a multiplier that kills database throughput under modest concurrent load. ISO 25010:2011 performance-efficiency requires that repeated reads of slowly-changing data not incur full round-trip costs each time. Without cache invalidation on post creation, you also risk serving stale feeds, but that is a secondary concern relative to the cost of zero caching.
Why this severity: Low because the impact accumulates gradually with user count and does not cause correctness failures, but the fix is low-effort and the cost of omission scales linearly with engagement.
community-social-engagement.activity-feeds.feed-caching-strategySee full patternA new user who signs up, lands on an empty feed, and sees nothing but 'No posts yet' has no path forward and churns within the first session — this is the single largest drop-off point on every social product. Empty feeds are the default state for every new account; treating them as an error case instead of an onboarding opportunity wastes the one moment the user is most willing to click Follow. Activation metrics live or die here.
Why this severity: Low because the feature works for active users, but the activation impact on new signups is disproportionately large.
community-social-engagement.activity-feeds.empty-state-onboardingSee full patternSynchronous in-line notification creation ties the success of a user action (like, follow, comment) to the reliability of a secondary side-effect. If the notification `INSERT` fails — due to a database timeout, connection exhaustion, or a schema constraint — the user sees an error for an action that actually succeeded. Under ISO 25010:2011 reliability, the primary mutation and the downstream notification are separate concerns with different fault tolerances. Coupling them in one request also adds latency: notification creation may involve email dispatch or push webhook calls, blocking the response for hundreds of milliseconds.
Why this severity: High because synchronous notification coupling turns transient notification failures into visible errors on high-frequency user actions, degrading the core interaction loop.
community-social-engagement.notifications.event-trigger-architectureSee full patternWithout a read-state column, every notification looks identical forever — the bell badge never clears, users learn to ignore it, and push/email digests repeat content the user already saw in-app. A single `read_at` timestamp unlocks unread counts, mark-all-as-read, per-channel deduplication across web and mobile, and analytics on notification effectiveness. Missing this field means every downstream notification feature is blocked until the schema is backfilled.
Why this severity: Low because notifications still deliver, but engagement quality and cross-channel deduplication are blocked without the field.
community-social-engagement.notifications.read-state-managementSee full patternOne notification per like scales linearly with your viral posts — a post with 500 likes creates 500 bell pings, and the user stops opening the app. Grouping collapses that into 'Alex and 499 others liked your post,' which preserves the signal without the noise. Without batching, push-notification open rates crater, email digests become unreadable, and heavy users disable notifications platform-wide rather than per-type, which you cannot recover from.
Why this severity: Medium because ungrouped notifications actively drive users to disable the channel, which is hard to reverse.
community-social-engagement.notifications.batching-groupingSee full patternSending every notification type to every user with no opt-out mechanism violates GDPR Art. 21 (right to object to processing) and GDPR Art. 7 (conditions for consent). CCPA §1798.120 grants similar opt-out rights. Beyond compliance, a platform that ignores preferences trains users to mute or uninstall the app rather than adjusting settings — a direct retention problem. Notification preference checks must happen before dispatch, not as a post-hoc filter; dispatching a notification and suppressing delivery still constitutes processing the personal data used to trigger it.
Why this severity: Low because the immediate harm is user experience degradation and notification fatigue, but the GDPR Art. 21 right-to-object exposure elevates the compliance risk above cosmetic severity.
community-social-engagement.notifications.channel-preferencesSee full patternRun this audit in your AI coding tool (Claude Code, Cursor, Bolt, etc.) and submit results here for scoring and benchmarks.
Open Social Features & Engagement Audit