Admin Dashboard Guide
The Objectuve Admin Dashboard is a standalone web application for managing users, reviewing moderation queues, and monitoring administrative activity. It runs at admin.objectuve.com (production) or localhost:5174 (local development).
See admin-site-map.md for a visual map of every route, the role required to load it, and how the role guard resolves.
Accessing the Dashboard
Production & Staging
| Environment | URL |
|---|---|
| Production | https://admin.objectuve.com |
| Staging | https://staging.admin.objectuve.com |
| Local | http://localhost:5174 |
Sign-In Methods
Clerk (Standard): Navigate to /sign-in and authenticate with your Clerk account (Google, Apple, or email). Your Clerk email must be in the admin allowlist.
Fallback (Testing & Outages): Navigate to /sign-in?fallback to access the email + secret form. This bypasses Clerk entirely.
| Field | Value |
|---|---|
josh@objectuve.com (or any admin email in the database) | |
| Auth Secret | The value of FALLBACK_AUTH_SECRET from the environment |
Fallback auth requires FALLBACK_AUTH_SECRET to be set on the backend. For local development, set it in your local, gitignored env file (copy rails_api/.env.example for the full variable template). For staging, set it as a Cloud Run environment variable.
Local Development Setup
# Terminal 1: Start the Rails API
cd rails_api
bundle exec rails s
# Terminal 2: Start the admin dashboard
cd admin_dashboard
npm run dev
# → http://localhost:5174If this is your first time, seed the database to create the admin user:
cd rails_api
bundle exec rails db:setup # Creates DB, runs migrations, seeds dataThis creates josh@objectuve.com as the admin user.
Dashboard Pages
Activity Log (Dashboard)
URL: /
The landing page shows a chronological feed of administrative actions performed across the platform. Each entry displays:
- Admin user who performed the action
- Action type (e.g.,
UPDATE_ROLES) - Target entity affected
- Timestamp of the action
- Metadata with before/after state details
When no actions have been logged yet, you'll see "No administrative actions tracked yet."
User Management
URL: /users
Search for any user by name, email, or exact ID. The search uses PostgreSQL trigram matching (pg_trgm) for fuzzy results.
Search: Type at least 2 characters in the search box. Results appear after a 400ms debounce.
Results table columns:
- Name — First and last name
- Email — Account email address
- Roles — Current admin roles (or "None")
- Joined — Account creation date
- Edit Roles — Opens the role editor
Role Editor: Click "Edit Roles" on any user to open a slide-over panel with checkboxes for each available role:
| Role | Access Level |
|---|---|
super_admin | Full platform access, can manage other admins |
admin | Standard admin access, user management, moderation |
moderator | Content moderation, report review |
support | Read-only access to user data for customer support |
Check/uncheck roles and click "Save" to update. Changes are logged in the Activity Log automatically via the Admin::Logged audit interaction.
AI Workforce (Live Ops Board)
URL: /ai-workforce · Roles: super_admin, admin
The AI Workforce landing (v4.8, OBJ-1562 route cutover) — a single command-center board over every in-flight and recent piece of AI-generated work ("tickets"), replacing the old separate agent-grid + Review Queue split. Auto-polls every 30 seconds (GET_WORKFORCE_BOARD). /ops-board and /review-queue (the pre-cutover URLs) redirect here.
Top bar: a "Live" status chip, a Board/Timeline segmented control (Timeline is disabled — "coming soon"), and five right-aligned stats: Need you, Agents active, Approval 7d, Spend MTD, Shipped 7d. Below ~1100px of board width the stats collapse behind an "Activity" toggle that opens the activity rail as a drawer.
Four columns, each a queue of tickets:
- Running — agents currently executing, with a live progress bar
- Needs review — pending artifacts and human-decision escalations awaiting a call
- Handed to you — tickets claimed via "Take it myself" or escalated directly to an operator
- Shipped today — delivered artifacts anchored to the current day (the backend's 7-day recent-artifact window is filtered client-side to "today" so the heading stays honest)
Activity rail (right side, collapses to a drawer on narrow boards): a reverse-chronological feed of board events — runs starting, approvals, rejections, hand-offs — each with a colored dot, actor, and relative time.
Clicking any ticket opens the Focus Detail overlay — see below.
Focus Detail overlay
Opens over the board (not a route of its own) when a ticket is clicked. A centered modal showing:
- Agent identity — avatar, name, role, ticket age, and a stage chip
- Queue position — "Reviewing N of M in your queue" with a progress bar, when the ticket is one of your actionable items
- Artifact body — the draft content;
code_patchartifacts render as a side-by-side diff (diff2html, output sanitized withDOMPurifyto prevent XSS) instead of raw text - Why it's here and Run trace (turns, duration, cost) side panels, with a link to the full Run Detail page
- Footer actions, depending on the ticket's stage: Approve & deliver / Approve — open PR / Send reply, Edit (inline text edit before approving; disabled for
code_patch— a diff isn't safely free-text-editable), Take it myself (claims the artifact via theclaimAiArtifactmutation, moving it to "Handed to you"), Reject (requires a reason, minimum 10 characters), and Pause agent for running tickets - Keyboard shortcuts:
J/Kor arrow keys to move through your queue,Aapprove,Eedit,Hhand off,Rreject,Escto close
On approve/reject, the overlay auto-advances to the next actionable ticket in your queue and shows a toast; if none remain, it closes.
Agents
URL: /agents · Roles: super_admin, admin
The secondary AI Workforce view — agent configuration, filters, and manual trigger/pause controls that the Ops Board doesn't replicate. This is the former /ai-workforce grid view, moved to its own route in the v4.8 cutover. Auto-polls every 30 seconds.
Employee list columns:
- Name — Employee display name and role
- Status — Active (green) or Paused (gray)
- Autonomy — Current level (shadow, semi_autonomous, autonomous)
- Budget — Monthly spend vs. cap
- Last Run — Timestamp of most recent execution
- Actions — Trigger run, Pause/Resume
Manual runs: Click "Trigger Run" to start an employee run on demand. The run dispatches to the Agent Runner service and results appear on the Ops Board.
Employee Detail
URL: /ai-workforce/:id
Deep dive into a specific AI employee, reached from either the Ops Board or the Agents grid:
- Approval rate chart — Chart.js bar chart showing weekly approve/reject rates
- Run history — 20 most recent runs with status, duration, token cost
- Memory state — Current JSONB memory entries (editable by admin)
- Promotion controls — Autonomy level management with confirmation dialog
- Tabs: Overview, Runs, Memory, Settings
Promotion criteria: Employees must meet approval rate thresholds over a minimum number of runs before promotion. The validate :promotion_requirements_met model validation enforces this at the database level.
Editing an employee's configuration: The Edit configuration button opens a modal that updates the employee's runtime config via the updateAiEmployee mutation. Editable fields:
- Name and Description
- Task prompt — the employee's standing instructions
- Model preference and Schedule (cron)
- Monthly budget (dollars → stored as cents)
- Skills — a comma-separated list of skill refs (
.claude/skills/{name}) baked into the Agent Runner image - MCP tools — toggle chips for the known servers (
github,posthog,sentry,mailtrap,slack). An employee can only invoke a tool whose server is enabled here (themcp_serversscoping rule) - Post-filter skill — the brand-voice validation skill applied to user-facing artifacts (blank for internal-only output)
Changes apply to the next run; an in-flight run keeps the config it started with. Autonomy level is managed separately via the Promotion controls. This editor is what makes the five seeded employees configurable without a redeploy (the gap closed by the OBJ-811 epic).
Run Detail
URL: /ai-workforce/:employeeId/runs/:runId
Execution trace for a specific AI employee run:
- Tool calls — Chronological list of MCP/REST tool invocations with inputs and outputs
- Artifacts — Generated content with approval status badges
- Token usage — Prompt/completion tokens and estimated cost
- Error logs — Failure reasons (max turns exceeded, budget exceeded, SDK errors)
Moderation Queue
URL: /moderation
Review pending content reports and community flags. This page is currently a placeholder — content moderation features will be ported from the Ionic admin view in a future phase.
Audit Log
URL: /audit-log · Roles: super_admin, admin
Global log of administrative actions (adminActions query). Filterable by acting admin (actorId, a user public_id), action type, and target type. Each row shows the acting admin, the action category, the target (rendered as the target's opaque public_id, never a raw integer DB id), a timestamp, and a JSON metadata blob.
Badge Stats
URL: /badges · Roles: super_admin, admin
Unlock-rate breakdown for every badge (badgeStats query), sorted highest-first. Surfaces which badges are common vs. rare across the user base — useful for tuning gamification difficulty.
Teams Monitoring
URL: /teams/monitoring · Roles: super_admin, admin
Read-only closed-beta monitoring for Teams V1 (adminTeamsMonitoring query, M11/PRIVACY-3). One row per team:
- Team — team name
- Seats — seats used vs. seat limit (
team.memberships.sizevs.subscription.seat_count) - Status — subscription status (
trialing/active/past_due/grace/canceled, or "no subscription") - Last active — most recent
updated_atacross the team's members, or "No activity yet"
Carries no personal goal, mood, or journal data — team-scoped operational fields only, per the data-boundary contract. See the Teams billing runbook for billing-incident procedures.
Content Reports
URL: /content-reports · Roles: super_admin, admin, moderator
User-submitted content reports awaiting review (contentReports query) — distinct from the auto-detected flags in the Moderation Queue. Each report card shows the reporter, the reportable type, reason, and a content preview; reviewing a report fires the reviewContentReport mutation. This is the only one of the four new views also accessible to the moderator role.
Plans
URL: /plans · Roles: super_admin, admin
Read-only view of configured subscription plans (plans query): name, slug, price (display + cents), billing interval, and active status. Mirrors the Stripe-backed plan catalog used by the Supporter tier.
Communities curation (editorial discovery)
URL: /communities/curation · Roles: super_admin, admin
Assigns communities to the three consumer-facing editorial discovery rails on /communities (behind the editorial-discovery flag — see Communities § Editorial Discovery Rails), and toggles the independent "featured" badge. CommunityCurationView.vue calls curatableCommunities for the list and two mutations, setCommunityEditorialSlot and setCommunityFeatured, to write — see Communities Deep-Dive § GraphQL Schema for the full contract on all three.
Workflow — assigning a community to a rail:
- Open
/communities/curation. The table lists every discoverable community (5+ members) plus any sub-threshold community that already holds a slot (see below), ordered by slot → position → name. - Find the community by name, or scan the rail overview strip at the top of the page — it shows the 3 named rails (Active now / Just started / Most members) with their 3 numbered positions each, and which community (if any) currently fills each one.
- In that community's row, pick a Rail slot from the dropdown (
— None —, Active now, Just started, Most members). - Enter a Position (1–3). The position field is disabled until a slot is chosen.
- Click Save. The row shows "Saving…", then the table refetches — the community now appears in that rail's position on the live
/communitiespage (subject to the 5-member floor below).
Choosing — None — and saving clears the community's slot and position, freeing that rail position.
Table columns: Community, Category, Members, Rail slot, Position, Featured, Status, row actions (Save/Revert or Retry/Revert once dirty).
Unsaved changes: editing a row's slot, position, or Featured toggle marks it "Unsaved" and reveals Save/Revert buttons — nothing writes until Save is clicked. Revert discards the staged edit and restores the row to its last-saved state. A row you haven't touched always reflects the latest server data, even if another admin's save changes it underneath you.
The 5-member discoverability floor. Communities need members_count >= 5 (Community::DISCOVERY_THRESHOLD, rails_api/app/models/community.rb:24) to render on any consumer discovery surface, including the editorial rails. A community can still be assigned a slot below that floor — the admin list is deliberately widened to include it (Resolvers::AdminQueries#curatable_communities, rails_api/app/graphql/resolvers/admin_queries.rb:229-238) so it never becomes invisible — but it won't render in the rail until it crosses 5 members. The curation screen shows an inline warning on that row ("N of 5 members. Name won't show in the rail until it hits 5 — leave it placed, or clear the slot to free the position.") so the admin can choose to hold the slot or free it up for another community. This also covers the reverse case: a community that shipped a slot and later drops below 5 members keeps its assignment, disappears from the consumer rail, but stays visible and clearable here rather than becoming an orphaned, invisible slot-holder.
Eviction on position collision — intentional. Only one community can occupy a given slot + position at a time. Assigning a slot/position that another community already holds silently evicts that community (clears its editorial_slot, editorial_position, and editorial_updated_at in the same transaction — Admin::SetCommunityEditorialSlot#apply_slot, rails_api/app/interactions/admin/set_community_editorial_slot.rb:45-58) and hands the position to the new one. This is deliberate — there is no "swap" or "insert and shift" affordance. The curation screen surfaces it after the fact with a dismissable toast naming both communities ("New community now holds Rail · Position. Evicted community held that spot and is back to unplaced.") so the admin isn't left wondering why a community they placed earlier disappeared.
is_featured is independent of editorial_slot. The Featured toggle sets a standalone boolean (Admin::SetCommunityFeatured, rails_api/app/interactions/admin/set_community_featured.rb) that drives the "Featured" badge shown on community cards elsewhere in the app (CommunityCardV2.vue) — it has no relationship to rail placement. A community can be featured with no editorial slot, slotted with no featured badge, both, or neither. Saving a row that changed both the slot and the Featured toggle fires both mutations; if one succeeds and the other fails, the row's status shows exactly which one didn't save ("Slot saved, Featured didn't" or "Featured saved, the slot didn't") so you know what to retry.
Row-level errors don't affect other rows — a failed save leaves that row's staged edits in place with a Retry/Revert choice, and every other row (saved or still-unsaved) is unaffected.
Role System (RBAC)
Objectuve uses a dual authorization model during the v1.3 transition:
- Legacy
adminboolean — The originalUser.adminflag. Users withadmin: truehave full admin access. - AdminRole records — The new RBAC system. Users can have multiple roles (
super_admin,admin,moderator,support) via theadmin_rolestable.
During the transition, a user is considered an admin if either admin: true OR they have at least one AdminRole record.
Checking access in code
# In GraphQL resolvers
require_admin! # Checks admin boolean + AdminRole records
# On the User model
user.admin? # Legacy boolean check
user.has_role?('admin') # RBAC check
user.super_admin? # admin? OR has_role?('super_admin')Audit Logging
All administrative actions are automatically logged via the Admin::Logged interaction. Every mutation that modifies user data, roles, or content wraps its logic in a Logged.call block that captures:
- Before state — Attribute snapshot before the change
- After state — Attribute snapshot after the change
- Actor — The admin who performed the action
- Metadata — Request IP, user agent, and action-specific details
Audit logs are immutable — they cannot be edited or deleted.
Fallback Authentication (Technical Details)
Fallback auth is a secret-based authentication mechanism that bypasses Clerk entirely. It exists for two scenarios:
- Testing — When Clerk isn't configured (local dev, CI)
- Clerk outages — Emergency access when Clerk is down
How it works
- Frontend stores
Fallback <secret>:<email>in localStorage - Apollo client sends it as the
SessionTokenheader (instead of a Clerk JWT) - Backend
GraphqlController#fallback_authvalidates the secret usingActiveSupport::SecurityUtils.secure_compare - If valid, looks up the user by email and sets
context[:current_user]
Security considerations
- Uses timing-safe comparison to prevent side-channel attacks
- Only active when
FALLBACK_AUTH_SECRETenvironment variable is set - Should NOT be set in production unless during a Clerk outage
- The secret should be rotated after each emergency use
- Admin dashboard rejects non-admin users even with a valid fallback token
Setting up for staging
# Generate a new secret
openssl rand -hex 32
# Set on Cloud Run
gcloud run services update enkidu-api-staging \
--set-env-vars="FALLBACK_AUTH_SECRET=<generated-secret>" \
--region=us-central1 \
--project=enkidu-488723Troubleshooting
"Authentication failed" on fallback login
- Verify
FALLBACK_AUTH_SECRETis set in your local env file (template:rails_api/.env.example) - Verify the Rails server is running on port 3000
- Verify the user exists:
bundle exec rails runner "puts User.find_by(email: 'josh@objectuve.com')&.admin" - Check the Rails server logs for auth errors
"This account does not have admin access"
The user exists but doesn't have admin privileges. Grant admin access:
cd rails_api
bundle exec rake users:make_admin EMAIL=user@example.comDashboard shows "No administrative actions tracked yet"
This is normal for a fresh database. Actions will appear after you perform admin operations (e.g., updating user roles).
Stale gem warnings on Rails startup
If you see "Source locally installed gems is ignoring..." warnings:
cd rails_api
rm -rf vendor/bundle/ruby/4.0.0/specifications/<stale-gem>*.gemspec
bundle installLast updated: 2026-08-07 — documented the Communities curation screen (/communities/curation): workflow, the 5-member floor and sub-threshold/orphan handling, eviction-on-collision, and the independent is_featured toggle (OBJ-2238).