Skip to content

Admin Evolution PRD — Operating a Hyper-Growth Platform with a Two-Person Team

Product: Objectuve (codename: Enkidu) Feature: admin_dashboard/ — evolution from launch-ready to scale-ready Status: Shipped — April 2026 Date: April 2026 Type: Infrastructure + Operations PRD Predecessor: Standalone Admin App PRD (Phase 0 largely shipped)


1. Context & Problem Statement

The standalone admin app exists. admin_dashboard/ is a working Vue 3 + Vite + Tailwind SPA with 11 functional views, Clerk auth, an AdminRole role model, an AdminAction audit log, Sentry + Lograge + PostHog telemetry, and an AI Workforce operations surface that already ships approved artifacts to GitHub, Mailtrap, and Slack. That investment — the "Phase 0 extraction" from the predecessor PRD — is done.

But the current tool was built for a pre-launch founder operating 0 users. Three things break the moment that founder opens it at 5K MAU, and all three break quietly — they degrade operator productivity and platform safety in ways that won't surface as errors until real damage has been done:

  1. Everything paginates to nothing. users returns .all. search_users hard-caps at 50. ai_employees, pending_artifacts, moderation_queue, feedback_stats have no cursor. admin_stats runs a 180-query uncached aggregate on every dashboard refresh. At 500 users each of these feels fine. At 5,000 they slow. At 50,000 they either timeout or lock the founder's Rails box while a check-in spike is landing.

  2. Roles exist in the database but not in the UI. AdminRole has super_admin | admin | support | moderator. The backend uses them. The frontend does not read them, does not gate routes by them, and treats every authenticated admin as a super_admin. The first support hire will see the same impersonation buttons, hard-delete links, and role editor that the founder sees — and the backend will reject some of those calls with a generic GraphQL error that reads like a bug. A "moderator can't ban a user" conversation should be designed into the UI, not discovered at 11 PM.

  3. Compliance tooling is a rake task. There is no gdpr_requests table, no export wizard, no cascade-preview for hard-delete, no appeals queue, no webhook event log, no refund workflow, no feature flag UI. Every one of these is either a legal obligation the moment a user submits the form on our privacy page, or an operational need the moment a Stripe webhook fails. Today they would all be solved by a founder SSH'ing into the Rails container to run rake tasks or open Stripe.api_key. That's a viable survival pattern. It is not a scaling pattern.

Separately, the admin app is operationally blind to its own behavior. It has no Sentry, no PostHog, no page-load tracking, no error boundary. When a support hire reports "the moderation queue didn't load," nobody can reconstruct what they did. When the GraphQL endpoint returns a 500 to the admin app because of a bad migration, the only signal is the support hire's screenshot.

The gap between what shipped and what a two-person team needs at 50K MAU is not large. It is specific. This PRD lists the specific augmentations, groups them into themes, phases them by the actual risk each unblocks, and explains why each one compounds into operator leverage rather than decoration.

What breaks if we don't close these gaps in 2026:

  • The first 100-user weekend from a Product Hunt launch fills the moderation queue with no bulk-ops affordance. A two-hour triage session becomes a Saturday-long one.
  • The first GDPR "delete my account" email creates 48 hours of panic code while we write a cascade-preview for User + Goal + GoalEvent + Community + AiEmployeeMemory + AdminAction deletions.
  • The first Stripe webhook failure takes out a Supporter subscription and the founder finds out via Discord message because there is no webhook log.
  • The first moderator hire takes an hour of onboarding per week forever because nothing in the UI tells them which buttons they can actually press.
  • AdminStatsService returns 20-second response times around 30K MAU, and the dashboard stops loading for anyone.

What this PRD is not about: rebuilding anything that already works. Dashboard, AI Workforce, Review Queue, Moderation, Analytics, Monitoring, Feedback are all functional. The evolution layers on top — pagination, RBAC enforcement, compliance wizards, observability, scale-readiness — without disturbing shipped surfaces.


2. Current State Audit

Audit performed against commit 84b4e4676 (2026-04-16). Evidence paths linked inline.

2.1 What exists in the admin surface

AreaStatusEvidence
Standalone Vue 3 + Vite + Tailwind app✅ Builtadmin_dashboard/
11 routes implemented, no empty shells✅ BuiltDashboard, Users, Moderation, Analytics, Monitoring, Feedback, AI Workforce, Employee Detail, Run Detail, Review Queue, Sign-In
Clerk OAuth + fallback token✅ Builtadmin_dashboard/src/views/SignIn.vue
AdminRole model (4 roles)✅ Builtrails_api/app/models/admin_role.rb
AdminAction audit log✅ Builtrails_api/app/models/admin_action.rb, rails_api/app/interactions/admin/logged.rb
AI Workforce ops surface✅ Builtadmin_dashboard/src/views/AIWorkforceView.vue, admin_dashboard/src/views/ReviewQueueView.vue
Backend search_users via ILIKE✅ Built (limited)rails_api/app/interactions/admin/user_search.rb
admin_stats service✅ Built (uncached)rails_api/app/services/admin_stats_service.rb
AI usage cost breakdown (per-feature, per-model, daily)✅ Builtrails_api/app/services/admin_ai_usage_service.rb
Health endpoint (DB, Redis, Sidekiq, LiteLLM)✅ Builtrails_api/app/controllers/health_controller.rb
Sentry (backend + Ionic frontend)✅ Builtrails_api/config/initializers/sentry.rb, ionic_frontend/src/sentry.ts
PostHog (Ionic frontend only)✅ Builtionic_frontend/src/posthog.ts
Lograge structured JSON logs✅ Builtrails_api/config/environments/production.rb:43-65
Slack budget alert for AI✅ Builtrails_api/app/jobs/ai_metrics/ai_budget_alert_job.rb
Crono scheduled jobs✅ Builtrails_api/config/cronotab.rb
Incident response runbook✅ Builtdocs/operations/incident-response.md, docs/operations/ai-runbook.md

2.2 Gaps classified by severity

P0 — Will break at 5K-10K MAU (launch quarter):

GapEvidenceConsequence
No pagination on any admin queryusers, search_users capped at 50, ai_employees, pending_artifacts, moderation_queue, admin_actions in rails_api/app/graphql/types/query_type.rbAt 5K users, user search returns truncated; at 50K it times out
AdminStatsService has no cacherails_api/app/services/admin_stats_service.rb — 180 queries per requestDashboard page load regresses linearly with MAU
No RBAC enforcement in frontend routeradmin_dashboard/src/router.ts has no role guardsModerator hire sees buttons they cannot use
No GDPR request queue or wizardsNo gdpr_requests table, no export/delete interactionsFirst right-to-be-forgotten = panic rake task
No bulk moderation actionsreview_content_flag is single-item onlyWeekend traffic spike overwhelms one-at-a-time triage
Admin app has zero observabilityNo @sentry/vue in admin_dashboard/package.jsonSupport-hire errors are invisible

P1 — Will break at 25K-50K MAU (year one):

GapEvidenceConsequence
No Stripe webhook event log or replayrails_api/app/controllers/webhooks/stripe_controller.rb logs to Rails logger onlyFirst webhook failure = silent subscription corruption
No refund workflowStripeService has no refund methodRefunds happen via Stripe Dashboard, not logged to AdminAction
No unified support inboxFeedback posts, content reports, AI artifact rejections, appeals are separate surfacesOperator alt-tabs through 4 views per ticket
No user 360° viewusers/:id view doesn't exist in admin_dashboardDiagnosing a user complaint means 6 GraphQL queries manually
No impersonation ("view as user")No interaction, no banner componentSupport cannot reproduce user's bug state
No feature flag UIFlags live in config/settings/*.yml, require deploy to flipKill-switch takes 8+ minutes, not 8 seconds
No soft-delete restoration UIacts_as_paranoid models (Goal, Community, GoalEvent, FeedbackPost, GoalMedia, UserDevice) all recoverable via rake onlyUser "I deleted by accident" = rake task
No Sidekiq web UI mountedrails_api/config/routes.rb has no mount Sidekiq::WebJob failures only visible via health endpoint stats
No command palette / global searchLayout has sidebar only, no Cmd+KEvery navigation is a click path
No breadcrumbs / env badge / unread countsadmin_dashboard/src/layouts/AdminLayout.vueProduction-vs-staging disaster risk
No pattern detection (repeat offender, flag rate spike)No analytics over content_flags by user or time windowAbusive user creates 20 flags before any human notices

P2 — Will limit the team to ~3 operators (year two):

GapEvidenceConsequence
No saved searches / segmentsNo persistence on user filtersEvery investigation rebuilds the query
No cohort retention / activation funnel surfacesNot in admin_stats_type.rbGrowth decisions rely on PostHog, not admin data
No per-user AI cost attributionAiUsageEvent lacks user_id filter in admin surfaceCannot investigate "why is this user costing $4/mo"
No webhook replay or retry visibilityIngress webhooks (Stripe, AI Workforce, Mailtrap) do not persist eventsFailed deliveries are invisible post-retry exhaustion
No scheduled digest to operatorOperator learns of queue depth by opening the appQueue depth surprises on Monday morning
No break-glass / dual-control for super_admin opsOnly Clerk step-up is documented, not implementedSingle compromised admin = full blast radius
No E2E tests in admin_dashboardadmin_dashboard/tests/ is unit-onlyRegressions from monorepo refactors not caught
No Cloud SQL read replica for admin queriesConfig not in placeAnalytics queries compete with user traffic at 50K+

3. Goals & Success Metrics

Primary goals

  1. Keep admin time-per-operator bounded at ≤ 45 min/day at 50K MAU. The anti-social philosophy applies to operators too. If the tool demands more, the tool is wrong.
  2. Eliminate "rake task panic mode" — any compliance, billing, or user-support action that today requires SSH must have a UI button by public launch (Sep 2026).
  3. Make roles real — moderator and support hires see only what they can use, with RBAC enforced at both router and mutation layers.
  4. Make the admin app observable to itself — Sentry + PostHog parity with the Ionic frontend, plus first-class tracking of admin actions as product events.
  5. Cap admin query performance degradation — p95 page load ≤ 1.5s through 100K MAU via pagination, caching, and (at 50K) a read replica.

Success metrics

Metrics are operator-efficiency and platform-health, never admin-engagement. Tracked quarterly.

MetricToday (Apr 2026)Q4 2026 (10K MAU)Q2 2027 (50K MAU)Q4 2027 (150K MAU)
Operator minutes / 1K MAU / day~15 (estimated)≤ 12≤ 6≤ 3
Time to triage one flag (p50)~90 s≤ 60 s≤ 30 s≤ 20 s
Time to run GDPR export (p95)"panic rake task"≤ 2 h≤ 30 min≤ 10 min
Time to flip a feature flag8+ min (deploy)≤ 30 s (UI)≤ 10 s≤ 10 s
% admin actions logged to AdminAction~60% (role ops only)100%100%100%
% admin actions visible to operator within 24 h100%100%100%
Dashboard p95 page load~2-3 s (uncached)≤ 1.5 s≤ 1.5 s≤ 1.5 s
Moderator onboarding time (to productive)~2 h (founder walkthrough)≤ 45 min≤ 30 min≤ 20 min
% destructive actions with typed confirmation0%100%100%100%
% destructive actions with 2FA / step-up0%≥ 50% (super_admin only)100% super_admin100% super_admin
Distinct active admin roles in production13 (super, mod, support)5 (add billing, read_only)6 (add team_admin)
Admin app has Sentry coverage
Stripe webhook events searchable last 30d
Admin runs against read replica

Guardrails

  • No admin action goes unlogged. Every mutation wraps Admin::Logged. This is a code-review rule and a model validation.
  • No admin mutation runs without RBAC check. Every resolver calls authorize_admin_role!(:role). Frontend gates are UX; backend gates are truth.
  • No admin query degrades user traffic. Any query touching > 1K rows must be paginated or cached or (phase 3) on the read replica.
  • No admin bulk action without typed confirmation. Select-N + delete is the single worst failure mode; always require typing the count or an identifier.
  • Operator telemetry is ops-only. PostHog events from admin origin go to a separate project group; they do not pollute user funnel analytics.

4. Strategic Rationale

Why evolve now, not at launch

The current admin is "pre-launch-founder-grade." It was designed for 0-500 users and a single operator. Public launch lands between June and August 2026, and post-launch timeline has three pivots that tighten the schedule:

  • Public launch → 500-2K MAU in the first month. Moderation volume, support email, and GDPR requests all arrive at once. Pagination and bulk actions are no longer optional on day 7.
  • First support/moderator hire in Q4 2026. Delegating a single button to a non-founder requires RBAC that is enforced in the UI, not just the backend. Making a hire productive in under a week requires the UI to hide what they cannot use.
  • Phase 7 Teams launch April-June 2027. Billing console and team-scoped admin are prerequisites. Without a refund workflow and webhook log, Teams goes live with operator risk proportional to MRR.

Every month of delay compounds — founder time is spent on rake tasks, support productivity is capped, compliance risk grows.

Why this is an evolution, not a rewrite

The hard work is done. admin_dashboard/ works. AdminRole + AdminAction work. AI Workforce works. The remaining gaps are additive: add pagination, add wizards, add RBAC guards, add observability packages, add caching. No architectural rewrite is required.

Phasing is therefore not "stand up the shell and port features" (the predecessor PRD's plan) but "fill specific named gaps in the order that each gap starts hurting."

Why observability for the admin app itself is non-negotiable

A tool for diagnosing user problems that cannot diagnose its own problems is a tool that costs operator time to investigate instead of saving it. Sentry + PostHog parity with ionic_frontend adds ~100 lines of code and costs < $5/mo at current scale. It pays back the first time a support hire reports a mystery error.

Why AI Workforce is the model for automation

The AI Workforce implementation proves the operator-leverage thesis in miniature. Five AI employees take scheduled runs, produce reviewable artifacts, and ship them to GitHub/Mailtrap/Slack after human approval. The admin's role is supervision, not doing the work. Every augmentation in this PRD should be framed the same way: the admin app should be the supervision surface for automations, not the place where humans do repetitive work.

Why not buy Retool or Forest Admin

Three reasons:

  • The business logic lives in the Interaction pattern. Retool/Forest wrap tables and expose columns; they don't know about Admin::Logged, acts_as_paranoid, or PublicRecord. Replicating our audit + soft-delete + public_id conventions in a third-party tool is the same effort as extending what we have.
  • PBC transparency obligations require custom surfaces (impersonation banner, PBC impact report). Retool cannot build those.
  • Admin mutations must hit the same GraphQL endpoint with the same authorization rails. Adding a REST escape hatch for Retool would break the security boundary.

The bespoke admin is cheaper at every horizon the PRD contemplates.


5. Proposed Evolution Scope (Module-by-Module)

Each subsection lists: what exists, the gap, the proposed evolution. Where a feature already has a predecessor PRD section, it's referenced rather than re-specified.

5.1 Scale-Readiness Foundation (universal prerequisites)

Exists: Backend enforces admin. Queries return full result sets or short hardcoded limits.

Gap: Nothing paginates. AdminStatsService runs uncached. No read replica. No rate limit on admin queries.

Evolution:

  • Cursor pagination on every list query: users, search_users, ai_employees, pending_artifacts, moderation_queue, admin_actions, feedback_stats.top_posts. Pattern: (first, after) argument, returns { edges, pageInfo { endCursor, hasNextPage } }. Use created_at + id tiebreaker (matches GoalEvent feed pattern).
  • Fragment caching in AdminStatsService using Rails.cache.fetch("admin_stats:#{days_back}", expires_in: 10.minutes) — stops the 180-query-per-request pattern immediately. Invalidate cache on new signup (optional; 10-min staleness is acceptable for operator dashboards).
  • pg_trgm indexes on users.email, users.username, users.display_name (migration already specified in predecessor PRD §6c, never applied).
  • Rack::Attack rules for admin origin: 120 queries/min per admin user, 30 mutations/min.
  • Read replica for admin analytics (phase 3, 50K MAU trigger). Rails multi-DB connected_to(role: :reading) middleware on GraphQL requests with admin origin header.
  • Background export: any admin query likely to scan > 10K rows (e.g., full audit log export) produces an AdminExport record and enqueues a Sidekiq job that writes to GCS with a signed URL.

5.2 RBAC Frontend Enforcement

Exists: AdminRole model. Backend authorize_admin!. search_users returns admin, adminRoles fields on User.

Gap: Frontend treats all admins as super_admin. No route guards. No button-level role checks.

Evolution:

  • useRoles() composable reading the signed-in admin's adminRoles from a cached me query; exposes hasRole(role), canPerform(action).
  • Route guards in admin_dashboard/src/router.ts using a meta: { requiresRole: ['moderator', 'admin'] } pattern; fallback view renders "Not authorized" with a contact path.
  • Sidebar nav filtering — items the current role cannot access are hidden, not disabled.
  • Button-level guards — destructive actions show disabled state with tooltip "requires super_admin" if the role lacks permission, rather than a GraphQL error.
  • Backend helper renameauthorize_admin_role!(role_symbol) in QueryType/MutationType that checks specific roles, not the generic admin?. Keep require_admin! as a derived any_admin_role? shim.
  • Role badge in topbar — admin sees their current role in the header at all times, avoiding "wait, which account am I signed in as" mistakes during dual-role debugging.

5.3 Observability-for-the-Admin-App

Exists: Backend Sentry, frontend Sentry on Ionic app, PostHog on Ionic app, Lograge on Rails.

Gap: Admin app has none of these. Errors invisible, navigation untracked, performance unmeasured.

Evolution:

  • @sentry/vue in admin_dashboard with a dedicated Sentry project (objectuve-admin). Vue router integration, browserTracingIntegration at 50% sample rate (admin traffic is low-volume), replay on errors at 100% sample.
  • PostHog with admin property tag — same API key as Ionic is acceptable, but all events are tagged surface: "admin" so they can be filtered out of user funnels. Track page views, critical button clicks (approve artifact, ban user, grant role), and timing metrics for the six heaviest views.
  • Admin app error boundary — a top-level <ErrorBoundary> component that captures render errors, logs to Sentry, and shows a "something broke, try again" fallback with a Sentry event ID for the operator to quote.
  • Apollo error link logs every GraphQL error to Sentry with the operation name as tag and the admin user ID as user context. Today errors are console.warn() only.
  • Frontend RUM — page load timing, Apollo query latency (p50/p95), cache hit ratio in a /system/health view in the admin itself.
  • Admin-action-to-PostHog bridge — every successful AdminAction row triggers a PostHog event (backend side, not frontend). This gives a unified "what admins did" timeline searchable by actor, action, target in PostHog without needing a dedicated admin-search UI for audit queries.

5.4 Compliance: GDPR + Privacy + Appeals

Exists: User has acts_as_paranoid soft-delete. That is the totality of GDPR tooling.

Gap: No request queue, no export wizard, no cascade-preview for hard-delete, no PII redaction policy for audit logs, no appeals surface.

Evolution (direct references to predecessor PRD §5k, §6e where aligned):

  • gdpr_requests table with request_type ∈ {export, deletion, rectification}, status ∈ {received, processing, fulfilled, failed}, due_by, export_file_url, fulfilled_by.
  • GDPR intake endpoint on user-facing app — /privacy/request posts a gdpr_request record. Rails confirms receipt within 72h (EU requirement).
  • Export wizard in admin — reviews request, shows all tables containing the user's data (pre-scanned list), generates signed GCS URL for JSON+CSV bundle, auto-expires in 30 days, logs to AdminAction.
  • Deletion wizard — shows cascade preview (which records delete, which soft-delete, which PII-redact in retained audit logs), requires typed email confirmation + super_admin step-up, runs in a transaction with a transaction-level audit note.
  • Post-deletion audit handlingAdminAction records touching a hard-deleted user retain target_user_hash (SHA256 of email) and strip PII from before_state/after_state. This preserves operator accountability without retaining user PII (predecessor PRD §9.8).
  • Appeals queue — a user-facing surface for appealing a moderation decision posts an ContentAppeal record. Admin appeals view is a filtered variant of the moderation queue.

5.5 Pagination + Filters + Bulk for Moderation

Exists: moderation_queue returns pending flags + reports limited to 50 each. review_content_flag / review_content_report operate on single items.

Gap: No filters (severity, age, flagged user, content type). No bulk actions. No pattern detection. No appeals.

Evolution:

  • Filter bar on admin_dashboard/src/views/ModerationView.vue: severity, source (profanity_filter/ai_screen/user_report), content type, age bucket, flagged user. Filters sync to URL params for shareability.
  • bulk_review_content_flags(flag_ids, action, reason) mutation — processes up to 50 flags atomically, each generating its own AdminAction. Typed confirmation required for > 20.
  • Pattern detection view (/moderation/patterns) — top N users by flag count in a rolling 7-day window; chart of flags/day; ability to open a "user flag cluster" detail that shows every flag against or from that user.
  • Moderation rules (predecessor PRD §6d) — ModerationRule table with pattern_type ∈ {regex, substring, ai_score_threshold}, action, severity. Editable via admin UI; applied by existing moderation interactions.
  • AppealsContentAppeal model; appeals queue is a tab on ModerationView.

5.6 User Management — 360° View, Impersonation, Lifecycle

Exists: SEARCH_USERS query, UPDATE_USER_ROLES mutation, admin_dashboard/src/views/UsersView.vue with role editor modal.

Gap: No user detail page. No timeline. No impersonation. No ban / unban / soft-delete / restore. No subscription panel.

Evolution:

  • /users/:id detail view in admin, with tabs:
    • Profile — basics, Clerk link, signup source, device count, last active.
    • Activity Timeline — merged stream of goals, events, mood logs, AI interactions, flags, AdminAction entries targeting this user. Paginated.
    • AI History — per-user Coach interaction log with cost attribution (depends on 5.7).
    • Flags & Reports — flags filed against them, flags they filed.
    • Subscription — plan, Stripe customer link, last payment, supporter_until, refund history (depends on 5.8).
    • Roles — current admin roles, grant/revoke form with reason (super_admin only).
    • Audit — every AdminAction where this user was target.
  • ban_user(user_id, reason, duration_days), unban_user(user_id, reason), soft_delete_user, restore_user, hard_delete_user(user_id, gdpr_request_id, typed_confirmation) interactions, all wrapping Admin::Logged.
  • Impersonationstart_impersonation(user_id, reason) mints a short-lived impersonation JWT, returns an app.objectuve.com URL in a new tab. User-side banner "an operator is viewing as you" (PBC transparency). Audit entries on start and end. Read-only in phase 2; write-enabled in phase 4 for support flows (predecessor PRD §9.7).
  • Soft-delete restoration UI — one-click restore for every acts_as_paranoid model from the user detail view, with reason required.

5.7 AI Workforce — From Shipped to Scale

Exists: Full shipped PRD. Five AI employees, approval workflow, artifact delivery to GitHub / Mailtrap / Slack, edit-distance tracking, autonomy promotion gates, monthly budget alerts.

Gap: No pagination. No filters on employee list or pending artifacts. No per-user or per-artifact cost attribution. No kill-switch UI. No prompt version comparison. No employee "retirement" / archive flow.

Evolution:

  • Pagination + filters on ai_employees (by status, autonomy, role_key) and pending_artifacts (by employee, kind, age).
  • Per-artifact cost surfacing in RunDetailView (today it's per-run only) — shows tokens and cents per artifact within the run, useful for expensive multi-artifact runs.
  • Per-user Coach cost view at /users/:id/ai-history — sums AiUsageEvent by user, filtered by feature; flags users whose monthly AI cost exceeds a configurable outlier threshold.
  • Kill-switch UI/ai-ops/kill-switches page with toggles for ai.coaching_enabled, ai.moderation_enabled, ai.batch_enabled, ai_workforce.runs_enabled. Wire to either a FeatureFlag model (see 5.9) or direct Settings write with audit. Currently requires a deploy to flip (AI runbook).
  • Prompt version diffcoach_prompt_versions query returning prompt template history per feature; side-by-side diff viewer in UI.
  • Employee archive — mutation to retire an employee (sets active=false permanently, preserves history, removes from default views).

5.8 Billing & Stripe Admin

Exists: StripeService with checkout, subscription cancel, portal URL. PaymentRecord model. Stripe webhook controller logs to Rails logger.

Gap: No webhook event log persisted. No replay. No refund workflow. No subscription inspection in admin. No MRR/churn dashboard. No manual reconciliation tool.

Evolution:

  • stripe_events tablestripe_event_id, type, payload jsonb, received_at, processed_at, status, error. Webhook controller persists every event before processing. Unique on stripe_event_id.
  • Webhook event log view at /billing/webhooks — paginated, filterable by type + status; detail view shows raw payload, processing result, replay button. Replay is a replay_stripe_event(event_id, reason) mutation that re-invokes the processor.
  • Subscription list + detailbilling_overview(period) (MRR, active subs, churn), subscription_detail(user_id) returning current + historical subscription state.
  • Refund workflowcreate_refund(subscription_id, amount_cents, reason, typed_confirmation) interaction. Calls Stripe::Refund.create. Refunds > $100 require step-up. Logs to AdminAction.
  • Reconciliation toolreconcile_stripe_subscription(id, reason) compares Stripe state to local PaymentRecord / User.supporter_until and corrects drift with audit entry.

5.9 Feature Flags (Runtime)

Exists: Settings.ai.coaching_enabled style flags in config/settings/*.yml, flipped by commit + CI deploy.

Gap: No runtime override. No audit. No per-user targeting. Deploy-to-flip cycle is 8+ minutes.

Evolution:

  • feature_flags tablekey, enabled, conditions jsonb (user IDs, roles, cohorts), updated_by, reason, updated_at.
  • FeatureFlag.active_for?(key, user) helper that first checks DB (cache-backed) then falls through to Settings. Gives runtime override with Settings as compile-time default.
  • Admin UI at /system/flags — list, toggle, edit conditions, log every change to AdminAction. Typed confirmation for kill-switch-category flags. Step-up for super_admin_required flags.
  • Change event bus — on flag flip, broadcast to Sidekiq to invalidate caches.

5.10 Support Inbox (Unified Triage)

Exists: Feedback posts (with status enum), content reports, AI artifact rejections are separate views.

Gap: Operator must rotate between views. No concept of "assignment." No reply templates. No ticket status beyond the source-specific enum.

Evolution:

  • support_tickets table (predecessor PRD §6f) with source, source_id, status, priority, assignee_id.
  • Ingestion — feedback posts, content reports, appeals, GDPR requests, direct-email (phase 3) auto-create tickets.
  • Unified inbox view at /support — paginated, filterable by assignee + status + source + priority. Detail view shows source context inline.
  • Assignment + status mutationsassign_ticket(id, assignee_id), update_ticket_status(id, status, reason), reply_to_ticket(id, template_id?, body).
  • Reply templatessupport_templates CRUD. Start with 5-10 templates for the most common issues.

5.11 System Operations

Exists: /health endpoint. Crono jobs in code. Sidekiq stats in health response.

Gap: No Sidekiq web. No Crono status view. No deploy history. No environment-variable inspector (redacted).

Evolution:

  • Mount Sidekiq web at /admin_api/sidekiq with same admin JWT gate. Or extract stats to GraphQL (sidekiq_stats, sidekiq_dead_jobs, sidekiq_retry_set) for a Vue-native view.
  • crono_jobs query — returns each job's last run, next run, last status, last duration. trigger_crono_job(job_id) for on-demand runs (super_admin only).
  • /system/deploys view — reads Cloud Run revision history via GCP API (service account with run.revisions.list). Shows current + previous + rollback trigger button. Rollback requires step-up.
  • /system/env view — environment variable viewer, values redacted by regex (/key|secret|token|password|dsn/i[REDACTED — ${length} chars]). Read-only.
  • Slow query surface — enable pg_stat_statements extension, expose top-20 queries by mean_exec_time in /system/queries.

5.12 Analytics — Cohorts, Funnels, North-Star

Exists: admin_stats returns top-level counts + daily growth + goal category breakdown. Ionic frontend has PostHog.

Gap: No cohort retention. No activation funnel computed server-side. No PBC impact report generator. No north-star metric overlay on the dashboard.

Evolution:

  • North-star card on dashboard home — cumulative goals completed trending toward the 2.5K → 1.5M → 10M overlay. First card operator sees each morning.
  • cohort_retention(cohort_month, resolution, weeks) — returns D1/D7/D30/D90 retention for a signup cohort, materialized nightly via Crono into a cohort_metrics table.
  • activation_funnel(cohort_month) — steps: signup → sync_user → first goal → first goal event → first streak → first supporter conversion. Materialized nightly.
  • pbc_impact_report(year) — year-in-review report with goals completed, unique users benefited, community-goal weeks achieved, moderation transparency metrics.
  • CSV export on every analytics surface (via AdminExport pattern from 5.1).

5.13 Layout / Navigation Improvements

Exists: Sidebar + topbar with Clerk avatar. Responsive.

Gap: No breadcrumbs. No env badge. No unread counters. No Cmd+K. No notification bell.

Evolution:

  • Environment badge in topbar — "production" (red), "staging" (yellow), "development" (gray). Driven by VITE_ENV.
  • Unread counters on sidebar items — moderation queue, review queue, support inbox. Driven by poll-every-30s pending_counts query (single query, not N).
  • Cmd+K command palette — global search across users, communities, flags, tickets; quick navigation; quick actions. Modeled on Linear.
  • Breadcrumb component — derived from router metadata.
  • Notification bell — triggered by NotifyOperatorJob pushing to a operator_notifications table (kill-switch events, new hard-delete, large refund, failing webhook, etc.). PostHog event emitted for every notification fired.

6. Data Model Changes

New migrations, in the order they'd be applied:

ruby
# gdpr_requests — 5.4
create_table :gdpr_requests do |t|
  t.references :user, foreign_key: true
  t.string :request_type, null: false   # export | deletion | rectification
  t.string :status, null: false, default: 'received'
  t.string :requestor_email, null: false
  t.datetime :received_at, null: false
  t.datetime :due_by, null: false
  t.datetime :fulfilled_at
  t.references :fulfilled_by, foreign_key: { to_table: :users }
  t.string :export_file_url
  t.datetime :export_expires_at
  t.text :notes
  t.timestamps
end

# stripe_events — 5.8
create_table :stripe_events do |t|
  t.string :stripe_event_id, null: false, index: { unique: true }
  t.string :event_type, null: false, index: true
  t.jsonb :payload, null: false
  t.string :status, null: false, default: 'received'  # received | processing | processed | failed
  t.datetime :received_at, null: false
  t.datetime :processed_at
  t.text :error
  t.integer :replay_count, default: 0
  t.timestamps
end

# feature_flags — 5.9
create_table :feature_flags do |t|
  t.string :key, null: false, index: { unique: true }
  t.boolean :enabled, null: false, default: false
  t.jsonb :conditions, default: {}
  t.references :updated_by, foreign_key: { to_table: :users }
  t.text :reason
  t.timestamps
end

# support_tickets — 5.10
create_table :support_tickets do |t|
  t.references :user, foreign_key: true
  t.references :assignee, foreign_key: { to_table: :users }
  t.string :source, null: false           # feedback_post | content_report | appeal | gdpr_request | direct
  t.string :source_type
  t.bigint :source_id
  t.string :status, null: false, default: 'new'  # new | in_progress | waiting_user | resolved
  t.integer :priority, default: 3                # 1-5
  t.text :last_message
  t.datetime :resolved_at
  t.timestamps
end

# content_appeals — 5.5
create_table :content_appeals do |t|
  t.references :user, null: false, foreign_key: true
  t.references :original_flag, foreign_key: { to_table: :content_flags }
  t.references :original_report, foreign_key: { to_table: :content_reports }
  t.text :appeal_reason, null: false
  t.string :status, null: false, default: 'pending'
  t.references :reviewed_by, foreign_key: { to_table: :users }
  t.datetime :reviewed_at
  t.text :review_notes
  t.timestamps
end

# moderation_rules — 5.5
create_table :moderation_rules do |t|
  t.string :name, null: false
  t.string :pattern_type, null: false    # regex | substring | ai_score_threshold
  t.text :pattern
  t.string :action, null: false          # flag | auto_reject | notify
  t.string :severity, null: false
  t.references :created_by, foreign_key: { to_table: :users }
  t.boolean :enabled, default: true
  t.timestamps
end

# cohort_metrics — 5.12
create_table :cohort_metrics do |t|
  t.date :cohort_month, null: false, index: true
  t.integer :day_offset, null: false
  t.integer :users_active
  t.integer :users_retained
  t.float :retention_rate
  t.datetime :computed_at, null: false
end

# admin_exports — 5.1
create_table :admin_exports do |t|
  t.references :admin_user, null: false, foreign_key: { to_table: :users }
  t.string :export_type, null: false
  t.jsonb :parameters, default: {}
  t.string :status, null: false, default: 'queued'
  t.string :file_url
  t.datetime :file_expires_at
  t.integer :row_count
  t.datetime :completed_at
  t.text :error
  t.timestamps
end

# operator_notifications — 5.13
create_table :operator_notifications do |t|
  t.references :admin_user, null: false, foreign_key: { to_table: :users }
  t.string :category, null: false
  t.string :severity, null: false    # info | warning | critical
  t.string :title, null: false
  t.text :body
  t.string :action_url
  t.datetime :read_at
  t.timestamps
end

# pg_trgm indexes — 5.1
enable_extension "pg_trgm"
add_index :users, "lower(email) gin_trgm_ops", using: :gin
add_index :users, "lower(username) gin_trgm_ops", using: :gin
add_index :users, "lower(display_name) gin_trgm_ops", using: :gin

# pg_stat_statements extension — 5.11
enable_extension "pg_stat_statements"

Schema-level shims:

  • AdminAction.before_state / after_state — add application-level PII-scrub helper on User deletion (keeps column, sanitizes).
  • User.admin boolean — derived method def admin?; admin_roles.active.exists?; end already in place; keep as deprecated shim.
  • AiUsageEvent — add user_id index for per-user cost attribution (5.7).

7. Phased Implementation

Phasing prioritizes each gap by when it starts to hurt, not by module coherence.

Phase A — Launch-Quarter Hardening (May-Jun 2026, 3-4 weeks)

Goal: Do not ship public launch without these.

  • 5.1 Scale foundation: pagination on all 6 core queries, AdminStatsService caching, pg_trgm indexes, Rack::Attack.
  • 5.2 RBAC frontend: useRoles(), router guards, sidebar filtering, role badge.
  • 5.3 Observability for admin: Sentry + PostHog + error boundary in admin_dashboard, Apollo error link, admin-action-to-PostHog bridge.
  • 5.4 GDPR minimum: gdpr_requests table, export wizard, deletion wizard, PII-scrub on audit log.
  • 5.5 Moderation bulk + filters (no pattern detection or rules yet).
  • 5.13 Layout: env badge, unread counters, breadcrumbs.

Exit criterion: public launch proceeds without rake-task fallback for any supported user action; first moderator hire can be onboarded in < 1h.

Phase B — First-90-Days Scale (Jul-Sep 2026, 4 weeks)

Goal: Operate a 500-2K MAU platform with bulk support requests arriving.

  • 5.6 User 360° view: /users/:id with all 6 tabs, ban/unban/soft-delete/restore mutations, impersonation (read-only).
  • 5.10 Support inbox: support_tickets table, unified view, assignment, 10 reply templates.
  • 5.11 System ops: Sidekiq web or stats query, Crono status, deploy history, env var viewer, slow query surface.
  • 5.13 Cmd+K command palette. Notification bell.

Exit criterion: first support hire is productive in their scoped role without super_admin access.

Phase C — Teams Launch Readiness (Oct-Dec 2026, 4-5 weeks)

Goal: Billing console + feature flags + advanced moderation before Teams GA.

  • 5.8 Billing: stripe_events log, replay, refund workflow, reconciliation.
  • 5.9 Feature flags: runtime table, admin UI, kill-switch category.
  • 5.5 Moderation patterns + rules.
  • 5.12 Cohort retention + activation funnel (materialized).
  • 5.7 AI Workforce: pagination + per-artifact cost + kill-switch UI.

Exit criterion: Teams launches with full billing and flag-management visibility.

Phase D — 50K MAU (Jan-Jun 2027)

Goal: Scale operational tax to stay bounded at ≤ 6 min / 1K MAU / day.

  • 5.1 Cloud SQL read replica + admin traffic routed.
  • 5.1 Background export for large data operations.
  • 5.4 Appeals queue.
  • 5.6 Impersonation write-enabled with confirmation.
  • 5.12 PBC impact report generator v1.
  • 5.7 Prompt version diff, employee archive.
  • E2E test coverage for GDPR, refund, bulk moderation, impersonation flows in Cypress.

Exit criterion: admin surface handles 50K MAU with < 1.5s p95 page load and < 6 min operator time per 1K MAU per day.

Phase E — 100K+ MAU (Jul 2027-onward)

Goal: Automation — the admin supervises rather than performs.

  • AI-assisted moderation (classifier inference + human confirmation; follows AI Workforce pattern).
  • Anomaly detection (flag-rate spike, cost anomaly, retention cliff) surfaced as operator notifications.
  • Federated SSO (SAML/OIDC) for enterprise team admins.
  • IP allowlist for super_admin actions (Cloud Armor).
  • Audit log archival to GCS cold storage after 90 days.
  • Multi-region / EU data residency (if needed for GDPR scale).

8. Observability & Scale Readiness (Cross-Cutting)

This section is intentionally separate because observability + performance cut across every module.

8.1 Admin app observability baseline (Phase A exit)

  • Sentry — dedicated objectuve-admin project, 50% traces, 100% replay on error, Vue router integration, user context = admin public_id + role list.
  • PostHog — admin events tagged surface: "admin" in a separate project group; track page views, critical actions, operation latencies.
  • Every admin mutation emits a PostHog event via Admin::LoggedPosthogNotifier.capture_admin_action. This is the unified audit-event stream across front and back.
  • Apollo error link — sends operation name, variables (PII-scrubbed), admin actor to Sentry.
  • Operator notificationsoperator_notifications table + sidebar bell + optional Slack webhook (phase B) for severity=critical.

8.2 Scale targets and triggers

TriggerAction
Any list query returns > 200 rowsAdd pagination in next sprint
Any service method > 1s p95Add cache in next sprint
Admin queries > 10% of total DB loadAdd read replica
Audit log > 10GBArchive to GCS cold storage
Sidekiq queue latency > 60sAdd Sidekiq worker concurrency
LiteLLM p95 > 2sInvestigate before next AI Workforce run

8.3 Alerting evolution

Existing alerts (Sentry error-rate, GCP uptime, AI budget) stay. Add:

  • Failed admin auth attempts — Sentry event when > 5 failures/10min from single IP.
  • hard_delete_user action — Slack page to #ops.
  • super_admin role grant — Slack page to #ops.
  • Kill-switch flag flip — Slack page to #ops.
  • Stripe webhook failure — Slack page if > 3 failures / 10 min.
  • Admin app page load p95 > 3s — Sentry performance alert.

8.4 Runbook attachment

Every alert channel message embeds a link to the specific runbook section:

8.5 Admin app self-health view

A new /system/self-health page in the admin_dashboard that renders:

  • Admin app build SHA + deploy timestamp
  • GraphQL endpoint reachable (current latency)
  • Sentry DSN valid + last event sent
  • PostHog endpoint reachable + last event sent
  • Clerk session valid + time-to-expiry
  • Admin user's role + permissions (for debugging own access)

Single place to answer "is my tool broken or is my target broken."


9. Security Considerations

Inherits the predecessor PRD §10 security model. Additions specific to this evolution:

  • Per-role rate limiting: Rack::Attack rules differ by role (moderator gets higher mutation rate, billing gets lower).
  • Step-up on hard-delete / refund / role grant / kill-switch: Clerk step-up or a dedicated stepUpToken parameter validated server-side.
  • Dual-control for super_admin critical actions (phase E): hard-delete of another admin, role revoke of super_admin, deploy rollback. Two super_admins must co-sign within 5 minutes.
  • Admin PostHog events PII-scrubbed: never send user email, password, session tokens, raw payload. Send public_ids + metadata keys.
  • Impersonation JWT scope: includes impersonated_by claim; backend logs every mutation performed during impersonation to AdminAction with impersonation_context: {operator_id, session_id}. User-facing banner renders impersonated_by in plain text.
  • Feature flag changes are audited + irreversible without audit: flipping off a user-facing flag on a live platform is a production change; log every change with reason + actor.

10. Cost Analysis

Incremental to predecessor PRD cost baseline.

ItemPhase A-BPhase C-DPhase E
Sentry admin projectfree tierfree tier~$26/mo
PostHog admin eventsfree tier< $10/mo~$50/mo
Postgres storage (new tables)+200MB+2GB+10GB
Cloud SQL read replica$0~$40/mo (D-custom-2-7680)~$80/mo
GCS cold-storage audit archive$0$0< $5/mo
Background export queue (GCS bandwidth)$0< $5/mo< $20/mo
Incremental total< $10/mo~$60/mo~$180/mo

Engineering cost (solo-with-Claude-pair):

  • Phase A: 3-4 weeks
  • Phase B: 4 weeks
  • Phase C: 4-5 weeks
  • Phase D: ongoing quarterly investment
  • Phase E: driven by ML + compliance needs, bounded by ROI per feature

The full evolution through Phase D is 12-16 weeks of work spread across 9-12 months. Compared to the cost of not doing it (rake-task firefighting, operator burnout, compliance risk, support churn), the ratio is overwhelmingly positive.


11. Edge Cases & Open Questions

  1. GDPR deletion + AiEmployeeMemory. AI employees have memory records that may reference users. Before hard-delete, cascade-preview must include AiEmployeeMemory rows; scrub vs delete policy TBD — proposal: delete rather than scrub since these are not accountability records.
  2. Impersonation + AI Workforce. Should an operator impersonating a user be able to trigger AI Workforce runs on their behalf? Phase D decision; propose read-only impersonation for AI surfaces.
  3. Feature flags vs Settings precedence. If FeatureFlag('ai.coaching_enabled').enabled? conflicts with Settings.ai.coaching_enabled, which wins? Proposal: DB wins for safety (can always disable); Settings is the compile-time default and is used only when DB has no row.
  4. Stripe event replay idempotency. Stripe webhook handler must be idempotent since replay will re-invoke it. Confirm existing stripe_controller.rb is idempotent before enabling replay; if not, add processed_event_ids deduplication.
  5. PostHog admin events retention. PostHog charges by event volume. Admin events are low-volume but we should set a 90-day retention policy in a separate project to bound cost.
  6. Crono job "trigger now" race conditions. If an operator triggers AiBudgetAlertJob manually while its scheduled run is also firing, dedup is via Rails.cache already. Confirm this extends to manual triggers too.
  7. Read replica read-your-writes. Admin actions that immediately re-read their own effect need to bypass the replica. Implement via opt-in query flag: .connected_to(role: :writing) { ... } on the read-your-writes queries.
  8. Command palette search performance at 100K users. Fuzzy-search 100K users client-side is not viable. Palette must proxy to backend searchUsers with debounced input.
  9. Notification bell overflow. At high notification volume, bell UI needs mark-all-read, filtering, and a "yesterday" / "last week" grouping rather than an unbounded list.
  10. Admin app bundle size. Adding Sentry, PostHog, command palette, chart libraries will push bundle > 500KB gz. Mitigate via route-level lazy loading and dynamic imports for heavy modules (Storybook, chart.js already lazy).

12. Verification

How we know each phase succeeded:

Phase A verification:

  • Run Lighthouse against each admin page: p95 load < 2s locally.
  • Seed 100K test users, search for one by partial email; response < 200ms.
  • Trigger admin_stats 100 times in a minute; no DB spike past baseline.
  • Sign in as a moderator role user; /users and /system/flags render "not authorized." Sidebar shows 6 items (dashboard, moderation, review queue, support, AI workforce, analytics); hides users, system, billing.
  • Fire a throw in DashboardView.vue; error boundary renders fallback + Sentry event ID appears in admin Sentry project inbox.
  • Submit a test GDPR export request; export wizard produces a JSON bundle within 2 minutes; download link expires after 30 days.
  • Select 20 flags in moderation; click bulk reject with reason; all 20 generate AdminAction entries; each entry has before_state + after_state + reason.

Phase B verification:

  • Impersonate a test user; banner renders "an operator is viewing as you" with operator's public_id; mutation attempts return 403 read-only error.
  • Hire-simulation: a new user with support role signs in; without founder's help, within 30 minutes they can: find a user by email, view their profile, respond to a support ticket from a template, mark it resolved.
  • Trigger Crono AiBudgetAlertJob manually from /system/crono; log entry appears within 5 seconds; result visible in admin without tailing Rails logs.

Phase C verification:

  • Replay a Stripe webhook event via UI; local payment record updates correctly; replay counted in stripe_events.replay_count.
  • Flip ai.coaching_enabled off via /system/flags; user Coach requests return kill-switch response within 10 seconds; audit log shows who and why.
  • Run cohort retention query for Jun 2026 cohort; D30 retention matches manual PostHog calculation within 2% tolerance.

Phase D verification:

  • Read replica handles 90% of admin query volume; primary DB CPU does not spike during admin analytics loads.
  • Run GDPR deletion of a test user; cascade preview matches actual row counts deleted/scrubbed; AdminAction retains target_user_hash but no PII.
  • Cypress E2E suite runs GDPR export → review → fulfill → expire pipeline without manual intervention.

13. Critical Files to Modify / Create

New Rails migrations (phased)

  • rails_api/db/migrate/{ts}_add_admin_query_trgm_indexes.rb
  • rails_api/db/migrate/{ts}_create_gdpr_requests.rb
  • rails_api/db/migrate/{ts}_create_stripe_events.rb
  • rails_api/db/migrate/{ts}_create_feature_flags.rb
  • rails_api/db/migrate/{ts}_create_support_tickets.rb
  • rails_api/db/migrate/{ts}_create_content_appeals.rb
  • rails_api/db/migrate/{ts}_create_moderation_rules.rb
  • rails_api/db/migrate/{ts}_create_cohort_metrics.rb
  • rails_api/db/migrate/{ts}_create_admin_exports.rb
  • rails_api/db/migrate/{ts}_create_operator_notifications.rb
  • rails_api/db/migrate/{ts}_enable_pg_stat_statements.rb

New Rails models

  • rails_api/app/models/gdpr_request.rb
  • rails_api/app/models/stripe_event.rb
  • rails_api/app/models/feature_flag.rb
  • rails_api/app/models/support_ticket.rb
  • rails_api/app/models/content_appeal.rb
  • rails_api/app/models/moderation_rule.rb
  • rails_api/app/models/cohort_metric.rb
  • rails_api/app/models/admin_export.rb
  • rails_api/app/models/operator_notification.rb

New Rails interactions (all wrapping Admin::Logged)

  • rails_api/app/interactions/admin/gdpr/export.rb
  • rails_api/app/interactions/admin/gdpr/delete_user.rb
  • rails_api/app/interactions/admin/ban_user.rb
  • rails_api/app/interactions/admin/unban_user.rb
  • rails_api/app/interactions/admin/impersonate_user.rb
  • rails_api/app/interactions/admin/restore_soft_deleted.rb
  • rails_api/app/interactions/admin/bulk_review_content_flags.rb
  • rails_api/app/interactions/billing/create_refund.rb
  • rails_api/app/interactions/billing/reconcile_subscription.rb
  • rails_api/app/interactions/billing/replay_stripe_event.rb
  • rails_api/app/interactions/admin/flags/set_feature_flag.rb
  • rails_api/app/interactions/support/assign_ticket.rb
  • rails_api/app/interactions/support/reply_to_ticket.rb

Rails files to modify

  • rails_api/app/graphql/types/query_type.rb — add pagination, authorize_admin_role!(role), 20+ new fields, replace all .all with cursor queries
  • rails_api/app/graphql/types/mutation_type.rb — register all new mutations
  • rails_api/app/services/admin_stats_service.rb — cache layer
  • rails_api/app/services/admin_ai_usage_service.rb — cache layer + user_id filter
  • rails_api/app/interactions/admin/logged.rb — emit PostHog event on success
  • rails_api/app/controllers/webhooks/stripe_controller.rb — persist events to stripe_events before processing
  • rails_api/config/initializers/sidekiq.rb — add dead-letter queue
  • rails_api/config/initializers/rack_attack.rb — new file, admin rate rules
  • rails_api/config/cronotab.rb — add nightly Analytics::MaterializeCohortsJob, Admin::ArchiveOldAuditsJob
  • rails_api/config/routes.rb — mount Sidekiq web with admin JWT gate (optional) or expose via GraphQL

New admin_dashboard files

  • admin_dashboard/src/views/UserDetailView.vue
  • admin_dashboard/src/views/gdpr/GdprQueueView.vue
  • admin_dashboard/src/views/gdpr/GdprExportWizard.vue
  • admin_dashboard/src/views/gdpr/GdprDeletionWizard.vue
  • admin_dashboard/src/views/billing/BillingOverview.vue
  • admin_dashboard/src/views/billing/WebhookEventsView.vue
  • admin_dashboard/src/views/billing/RefundView.vue
  • admin_dashboard/src/views/system/FeatureFlagsView.vue
  • admin_dashboard/src/views/system/DeployHistoryView.vue
  • admin_dashboard/src/views/system/EnvVarsView.vue
  • admin_dashboard/src/views/system/CronoStatusView.vue
  • admin_dashboard/src/views/system/SlowQueriesView.vue
  • admin_dashboard/src/views/system/SelfHealthView.vue
  • admin_dashboard/src/views/support/SupportInboxView.vue
  • admin_dashboard/src/views/analytics/NorthStarView.vue
  • admin_dashboard/src/views/analytics/CohortRetentionView.vue
  • admin_dashboard/src/views/moderation/PatternsView.vue
  • admin_dashboard/src/composables/useRoles.ts
  • admin_dashboard/src/composables/usePendingCounts.ts
  • admin_dashboard/src/composables/useOperatorNotifications.ts
  • admin_dashboard/src/components/layout/CommandPalette.vue
  • admin_dashboard/src/components/layout/EnvironmentBadge.vue
  • admin_dashboard/src/components/layout/NotificationBell.vue
  • admin_dashboard/src/components/layout/Breadcrumbs.vue
  • admin_dashboard/src/components/ErrorBoundary.vue
  • admin_dashboard/src/sentry.ts — dedicated admin Sentry init
  • admin_dashboard/src/posthog.ts — admin-tagged PostHog init

admin_dashboard files to modify

  • admin_dashboard/src/router.ts — add meta.requiresRole, guards using useRoles()
  • admin_dashboard/src/layouts/AdminLayout.vue — env badge, unread counters, notification bell, role badge, breadcrumbs
  • admin_dashboard/src/apollo-client.ts — error link to Sentry
  • admin_dashboard/src/views/UsersView.vue — cursor pagination, link to /users/:id
  • admin_dashboard/src/views/ModerationView.vue — filter bar, bulk-select, bulk-action button
  • admin_dashboard/src/main.ts — mount Sentry, PostHog, ErrorBoundary
  • admin_dashboard/package.json — add @sentry/vue, posthog-js

Documentation

  • docs/operations/billing-runbook.md (new)
  • docs/operations/gdpr-runbook.md (new)
  • docs/operations/rbac-roles.md (new) — canonical role definitions
  • docs/features/admin-dashboard.md (update) — reflect new capabilities
  • docs/product/standalone-admin-app-prd.md (update) — mark Phase 0 as "shipped, see admin-evolution-prd.md"
  • docs/product/prd-index.md (update) — add this PRD
  • CLAUDE.md — add env vars for admin Sentry DSN and admin PostHog key

14. How This PRD Ladders Up to the North Star

Per docs/product/north-star.md, the metric is cumulative goals completed. This PRD does not create goal completions directly. It protects them:

  1. Operator time saved → founder time preserved for product work. Every minute the founder spends in moderation bulk-review instead of per-flag triage is a minute spent shipping the next retention feature.
  2. Compliance reliability → trust preserved. A user whose GDPR deletion takes 72 hours instead of 2 weeks is a user who stays. A user who thinks we dropped the ball is not.
  3. Platform safety → feed quality preserved. Faster moderation + pattern detection → healthier communities → higher goal-event engagement → more goals completed.
  4. Operator headcount leverage. One moderator and one support hire managing 50K MAU beats 5 operators managing the same load. Every hour we don't burn on operations is an hour we can invest back into the north-star.

The north-star card on the admin dashboard home stays the single most prominent piece of UI. It is the reminder — every time an operator opens this tool — of what all this plumbing is for.



Objectuve Softworks, LLC (Delaware Public Benefit LLC; principal office in Chicago, IL)Date: 2026-04-16

Last updated: 2026-07-16

Loading…