Screens — Manage App
Audience: Super admins — platform operations, tenant management, global configuration
Platform: Web only (Next.js)
Auth: super_admin role required
Status notation used throughout this doc:
- [Live] — built and working in
manage-dev.leadmetrics.ai(verified 2026-03-31) - [To Build] — specified but not yet implemented; part of the agent architecture layer
Sidebar Navigation
Collapsible — toggle button (< / > chevron) collapses to icon-only mode (w-16); expanded width is w-56. State is client-side only (resets on reload). Implemented in apps/manage/src/components/manage-sidebar.tsx — a "use client" component; NAV array lives there, not in the layout.
Dashboards ▾ ← topmost group (Apr 2026)
Platform Overview → /dashboards/overview [Live]
Execution Queue → /dashboards/execution-queue [Live]
Finance → /dashboards/finance [Live]
AI Performance → /dashboards/ai [Live Apr 2026]
Tenant Health → /dashboards/tenant-health [Live Apr 2026]
Content Pipeline → /dashboards/content-pipeline [Live Apr 2026]
Channel Health → /dashboards/channel-health [Live Apr 2026]
Goal & Strategy → /dashboards/goal-strategy [Live Apr 2026]
Tenants → /tenants [Live]
Users → /users [Live]
Agents → /agents [Live] (tabs: All Agents | Analytics)
Agent Chat → /chat [Live]
Skills → /skills [Live]
Content ▾
Blog Posts → /blog-posts [Live]
Social Posts → /social-posts [Live]
Templates ▾
Email → /templates/email [Live]
Telegram → /templates/telegram [Live]
Billing ▾
Invoices → /invoices [Live]
Plans → /plans [Live] ← full CRUD incl. create/edit/delete
Offerings → /plans/offerings [Live] ← create/edit/delete offerings
Regions → /plans/regions [Live] ← create/edit/delete regions
Credits & Usage → /usage [Live]
LLM Costs → /costs [Live]
Transactions → /transactions [Live]
Backlink Directories → /directories [Live]
Push Notifications → /push-notifications [Live]
Audit Logs → /audit-logs [Live]
System ▾
RAG & AI Config → /system/rag-config [To Build]
Autonomy → /system/deliverable-settings [Live] ← per-content-type DM review toggles + activity volume cap
Important Days → /system/important-days [Live]
Help Center → /help [Live]Top bar [Live]: theme toggle (icon cycles light → dark → system) · notification bell (violet unread badge, full dropdown panel) · profile avatar dropdown (initials, name, email, “Super Admin” badge, Profile Settings link, Sign out). All in apps/manage/src/components/topbar.tsx.
Notification Dropdown [Live]
- Fetches from
GET /api/admin/notificationson mount (once per page load); no Socket.IO real-time (manage namespace does not broadcast per-tenant notification events) - Shows last 50 notifications across all tenants (super admin cross-tenant view)
- “Mark all read” →
POST /api/admin/notifications/read-all
API routes (Next.js proxy):
GET /api/admin/notifications→ proxies toGET /admin/v1/notificationsPOST /api/admin/notifications/read-all→ proxies toPOST /admin/v1/notifications/read-all
Backend routes (/admin/v1/, requireSuperAdmin):
GET /admin/v1/notifications— last 50 notifications across all tenants, newest firstPOST /admin/v1/notifications/read-all— marks all unread notifications as read platform-wide
Screen M1a — Platform Overview (/dashboards/overview) [Live]
Purpose: Real-time pulse of the entire platform — agent activity, LLM spend, queue health. Moved from /overview to /dashboards/overview (Apr 2026); old URL redirects.
┌─────────────────────────────────────────────────────────────┐
│ Platform Overview Super Admin │
├──────────┬──────────┬──────────┬───────────────────────────┤
│ Tenants │ Active │ Total │ System Health │
│ Total │ Agents │ LLM Spend│ ✅ All services OK │
│ 24 │ 47 live │ $1,204 │ Queue: 12 jobs pending │
├──────────┴──────────┴──────────┴───────────────────────────┤
│ Tenant Activity (last 24h) Spend by Tenant │
│ ───────────────────────── ───────────────────── │
│ Acme Corp 14 activities Acme ████ $412 │
│ Globex 9 activities Globex ██ $204 │
│ Initech 3 activities Initech █ $88 │
└─────────────────────────────────────────────────────────────┘Live data: Agent counts and queue depths via SSE. Spend totals via polling.
Clicking a tenant row navigates to Tenant Detail (M3).
Screen M1b — Execution Queue (/dashboards/execution-queue) [Live]
Purpose: Cross-tenant, cross-agent log of every agent run with full debug details — input prompt, output, execution transcript, token usage, cost.
List page (/dashboards/execution-queue)
Header: “Execution Queue” heading + total run count badge.
Filter bar:
- Agent role dropdown (all roles)
- Tenant selector
- Status filter (pending / generating / done / failed)
- Date range picker
Table columns: Agent · Tenant · Status · Model · Tokens (in/out) · Cost · Duration · Started At
UX: IntersectionObserver infinite scroll (no pagination buttons). Row click navigates to detail page.
API: GET /admin/v1/runs with cursor + filters → proxy at apps/manage/src/app/api/runs-proxy/route.ts
Detail page (/dashboards/execution-queue/[runId])
Sections (top to bottom):
- Header — agent name, run ID, status badge, back button
- Stats bar — model, duration, cost, token counts (input / output)
- Input Prompt — full text of the prompt sent to Claude (from
AgentRun.inputPrompt) - Output — Preview (rendered markdown) / Source (raw) tabs
- Execution Log —
TranscriptViewercomponent; shows every turn of the Claude Code session - Run Metadata — skills array, tenant ID, timestamps
Transcript entries (TranscriptViewer in [runId]/TranscriptViewer.tsx):
assistant— violet, expanded by defaulttool_use— amber, collapsed; header shows tool nametool_result— emerald, collapseduser— blue, collapsedsystem— gray, hidden by default (toggle to show)error— red, expanded by default
API: GET /admin/v1/runs/:runId → proxy at apps/manage/src/app/api/runs-proxy/[runId]/route.ts
Screen M1c — Finance (/dashboards/finance) [Live]
Purpose: CFO-level financial health view — revenue, collections, costs, and outstanding debt in one page. All data computed live via direct DB queries (no API hop). For trend charts and gross margin, see Option B in docs/reports/cfo.md.
File: apps/manage/src/app/(manage)/dashboards/finance/page.tsx (async server component, force-dynamic)
Sections
Revenue KPIs (7 stat cards in a responsive grid):
- Monthly Recurring Revenue — sum of active subscription plan prices, normalised to monthly (annual ÷ 12), grouped by currency
- Annual Run Rate (ARR) — MRR × 12
- Collected This Month — sum of
grossAmountfor invoices withpaidAtin the current UTC calendar month - Collection Rate — collected ÷ total issued this month; color-coded green ≥90%, amber ≥70%, red <70%
- Outstanding AR — sum of
netPayablefor allpending+overdueinvoices, grouped by currency - Overdue Invoices — count of overdue invoices across all time
- LLM Cost (30 days) — sum of
AgentRun.costUsd(USD float, not smallest unit)
Revenue by Plan Tier: Table — Plan (Starter/Professional/Agency/Enterprise) · Tenants · Monthly Revenue · Share of MRR · mini bar
Top 5 Tenants by Revenue + Top 8 Tenants by LLM Cost (30d):
Side-by-side tables, each row links to /tenants/:id
Outstanding Invoices: Table — Tenant · Outstanding amount · Status badge (Pending amber / Overdue red) · Due date · Age in days (red when overdue). Sorted by amount descending, top 10 tenants.
Data Model Gotchas
- Invoice
grossAmount/netPayable/Plan.priceAmount— all in smallest currency unit (paise/cents), divide by 100 to display AgentRun.costUsd— USD float (e.g. 0.025), do NOT divide by 100- Multi-currency: MRR/AR displayed as separate figures per currency, e.g. “₹4,99,000 + $200” — gross margin % intentionally omitted (requires forex rate)
- Month start uses UTC string
${y}-${m}-01T00:00:00.000Z(notnew Date(y,m,1)) to avoid timezone shift
Future: Option B
docs/reports/cfo.md specifies the PlatformFinancialSnapshot model and daily scheduler job that will unlock 12-month MRR trend charts and gross margin % once implemented.
Screen M1d — AI Performance (/dashboards/ai) [Live Apr 2026]
Purpose: Operational health of the AI layer — model usage, agent success/failure rates, cost efficiency, and token consumption. Cross-tenant, 30-day rolling window. All data from the AgentRun table; no new schema required.
File: apps/manage/src/app/(manage)/dashboards/ai/page.tsx (async server component, force-dynamic)
Nav: Fourth child of Dashboards NavGroup (BrainCircuit icon)
Sections
Header KPIs (6 stat cards):
| Card | Value | Source |
|---|---|---|
| Total Runs (30d) | count | agentRun.count |
| Overall Success Rate | completed / total % | groupBy(status)._count |
| Total LLM Cost (30d) | sum costUsd USD | agentRun.aggregate._sum.costUsd |
| Avg Cost per Run | total cost / run count | derived |
| Total Tokens (30d) | sum inputTokens + outputTokens | agentRun.aggregate._sum |
| Models in Use | count of distinct model values | groupBy(model) |
Success rate color-coded: ≥ 95% green, ≥ 85% amber, < 85% red.
Runs over Time — 30-day bar chart:
- Fetch
{ startedAt, status }for all runs last 30d, bucket by UTC date in JS - Stacked bars: completed (violet) + failed (red) per day
- Shows daily run volume and failure spikes at a glance
Performance by Agent Role (table):
| Column | Source |
|---|---|
| Role | agentRole |
| Runs (30d) | _count |
| Success Rate | completed count / total % |
| Avg Cost / Run | _avg.costUsd |
| Avg Duration | _avg.durationMs formatted as s/m |
| Avg Tokens | _avg.inputTokens + _avg.outputTokens |
Sorted by run count descending. Each row links to /dashboards/execution-queue?agentRole={role}.
Model Distribution (table):
| Column | Source |
|---|---|
| Model | model |
| Runs | _count |
| Total Cost | _sum.costUsd |
| Avg Cost / Run | _avg.costUsd |
| % of Runs | count / total |
Adapter Health (table, only rendered if > 1 distinct adapter):
| Column | Source |
|---|---|
| Adapter | adapter |
| Runs | _count |
| Failed | failed count |
| Failure % | failed / total — red when > 5% |
DB Queries (all AgentRun, 30d window, parallel)
const [
statusBreakdown, // groupBy(status) → _count
byRole, // groupBy(agentRole, agentName) → _count, _avg.costUsd, _avg.durationMs, _avg.inputTokens, _avg.outputTokens + failed sub-count
byModel, // groupBy(model) → _count, _sum.costUsd, _avg.costUsd
byAdapter, // groupBy(adapter) → _count + failed sub-count
costAgg, // aggregate → _sum.costUsd, _sum.inputTokens, _sum.outputTokens, _count
runsForChart, // findMany({ select: { startedAt, status } }) → bucketed by day in JS
] = await Promise.all([...]);byRole requires two queries: one groupBy for averages, one filtered where: { status: "failed" } groupBy for failure counts (Prisma groupBy cannot filter within groups).
Distinctions from /agents?tab=analytics
/agents?tab=analytics | /dashboards/ai |
|---|---|
| Per-agent config view with analytics tab | Pure metrics view, no config |
| Period picker (7/30/90d) | Fixed 30d rolling window |
| Adapter/model breakdown charts | Same + agent role performance table |
| No failure rate per role | Failure rate prominently surfaced |
| No daily run volume chart | 30-day bar chart |
Screen M1e — Tenant Health (/dashboards/tenant-health) [Live Apr 2026]
Purpose: Operational health view across all tenants — who is active and engaged, who is stalled, who is locked out, and how onboarding is progressing. Gives a single-screen signal for churn risk and expansion opportunities.
File: apps/manage/src/app/(manage)/dashboards/tenant-health/page.tsx (async server component, force-dynamic)
Nav: Fifth child of Dashboards NavGroup (HeartPulse icon)
Sections
Overview KPIs (top row, 5 stat cards):
| Card | Value | Source |
|---|---|---|
| Total Tenants | all statuses | tenant.count |
| Active | status = “active” | groupBy(status) |
| Onboarding | status = “onboarding” | groupBy(status) |
| Locked Out | subscription.isLockedOut = true AND subscription.status = "active" | subscription.count |
| New This Month | createdAt >= UTC month start | tenant.count |
Engagement KPIs (second row, 2 stat cards):
| Card | Value | Color rule |
|---|---|---|
| Engaged (30d) | active tenants with ≥ 1 agent run in last 30d, shown as N (X%) | ≥ 70% green, ≥ 50% amber, < 50% red |
| Stalled | active tenants with NO agent run in last 30d | > 0 = amber; > 10 = red |
Onboarding Pipeline (table):
Tenants where status = "onboarding", ordered by createdAt asc (oldest first — most at-risk).
| Column | Source |
|---|---|
| Tenant | name, link to /tenants/{id} |
| Source | createdVia — “self-signup” or “admin” |
| Days in Onboarding | (now - createdAt) in days |
| Onboarding Completed | tick if onboardingCompletedAt != null; date if set, ”—” if not |
Empty state: “No tenants in onboarding”.
Stalled Tenants (table):
Active tenants whose most recent agentRun.startedAt is either null (never ran) or older than 30 days. Ordered by last run date asc (longest-stalled first).
| Column | Source |
|---|---|
| Tenant | name, link to /tenants/{id} |
| Plan | subscription → plan.name (active subscription, latest) |
| Last Run | formatted date, or “Never” |
| Days Stalled | now - lastRun in days, or ”—” for never-ran |
Show max 20 rows. If > 20 stalled, show count badge: “Showing 20 of N — view all in Tenants”.
Empty state: “All active tenants have run agents in the last 30 days ✓”.
Locked Out Subscriptions (table):
subscription.isLockedOut = true AND subscription.status = "active". Ordered by lastLockedAt asc.
| Column | Source |
|---|---|
| Tenant | tenant.name, link to /tenants/{id} |
| Plan | plan.name |
| Locked At | lastLockedAt formatted |
| Reason | lockoutInfo.reason (from Json field) |
| Link | ”View →” to /tenants/{id} |
Empty state: “No subscriptions are currently locked out ✓”.
Plan Distribution (table):
All plans with at least 1 active subscription, grouped by tier.
| Column | Source |
|---|---|
| Plan | plan.name |
| Tier | plan.tier (0–3) |
| Active Subscriptions | _count of subscriptions where status = "active" |
Ordered by plan.tier asc then name asc.
DB Queries (7 parallel, force-dynamic)
const now = new Date();
const last30d = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
const y = now.getUTCFullYear();
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
const monthStart = new Date(`${y}-${m}-01T00:00:00.000Z`);
const [
statusBreakdown, // tenant.groupBy(status)._count
newThisMonth, // tenant.count({ createdAt >= monthStart })
lockedOutSubs, // subscription.findMany({ isLockedOut: true, status: "active" }) + tenant select
onboardingTenants, // tenant.findMany({ status: "onboarding" }) ordered by createdAt asc
activeTenants, // tenant.findMany({ status: "active" }) + latest active subscription → plan
lastRunPerTenant, // agentRun.groupBy(tenantId)._max.startedAt — used for stalled detection
planDistribution, // plan.findMany with _count.subscriptions where status="active"
] = await Promise.all([...]);JS derivations after queries:
// Last run lookup
const lastRunMap = new Map(lastRunPerTenant.map(r => [r.tenantId, r._max.startedAt]));
// Engagement split
const engagedTenants = activeTenants.filter(t => {
const last = lastRunMap.get(t.id);
return last !== undefined && last !== null && last >= last30d;
});
const stalledTenants = activeTenants
.filter(t => {
const last = lastRunMap.get(t.id);
return !last || last < last30d;
})
.sort((a, b) => {
const la = lastRunMap.get(a.id)?.getTime() ?? 0;
const lb = lastRunMap.get(b.id)?.getTime() ?? 0;
return la - lb; // oldest first
});Critical data model notes:
- Tenant
statusvalues:"onboarding"|"active"|"suspended"|"churned"(NOT “locked” — lockout is on Subscription viaisLockedOut) - Use
subscription.isLockedOutfor “locked out” metric, NOTtenant.status - Multiple subscriptions per tenant — always filter
subscription.status = "active"and takeorderBy: { createdAt: "desc" }, take: 1for the current plan lockoutInfoisJson?— access(sub.lockoutInfo as { reason?: string })?.reasonwith a type cast
Screen M1f — Content Pipeline (/dashboards/content-pipeline) [Live Apr 2026]
Purpose: Delivery team view — tracks how much content is being produced, where it is in the approval pipeline, and how social publishing is performing. Cross-tenant.
File: apps/manage/src/app/(manage)/dashboards/content-pipeline/page.tsx (async server component, force-dynamic)
Nav: Sixth child of Dashboards NavGroup (Layers icon)
Sections
This Month KPIs (5 cards):
| Card | Value | Color rule |
|---|---|---|
| Content Created | total blog + social + newsletter createdAt >= monthStart | neutral |
| Published / Sent | blog publishedAt + social publishedAt + newsletter sentAt >= monthStart | green if > 0 |
| DM Review Backlog | sum of dm_review status across all 3 types | warn if > 0, danger if > 20 |
| Client Review Backlog | blog client_review + social client_review | warn if > 0, danger if > 10 |
| Publish Failures | social publish_failed current count | danger if > 0 |
Pipeline Status by Type (table):
One row per content type. Status buckets:
| Stage | Blog | Social | Newsletter |
|---|---|---|---|
| DM Review | dm_review | dm_review | dm_review |
| Client Review | client_review | client_review | — (N/A) |
| In Progress | dm_approved + client_approved | dm_approved + client_approved + active + scheduled + publishing | draft + approved + sending |
| Live / Sent | published | published | sent |
| Rejected / Failed | rejected | rejected + publish_failed | failed |
Content Created — 30-day bar chart:
findMany({ createdAt >= last30d, select: createdAt })for all 3 types- Stacked bars per day: blog (emerald, bottom) · social (violet, middle) · newsletter (sky, top)
- Same UTC-day bucketing pattern as AI Performance dashboard
Social Publishing Health (table, last 30d):
socialPost.groupBy(platform, status)wherestatus in [published, publish_failed]ANDpublishedAt >= last30d- Columns: Platform | Published | Failed | Failure Rate (amber ≤5%, red >5%)
- Only rendered if any data exists
DB Queries (13 parallel)
const [
blogByStatus, // blogPost.groupBy(status)._count
socialByStatus, // socialPost.groupBy(status)._count
newsletterByStatus, // emailNewsletter.groupBy(status)._count
blogCreatedMonth, // blogPost.count({ createdAt >= monthStart })
socialCreatedMonth, // socialPost.count({ createdAt >= monthStart })
newsletterCreatedMonth, // emailNewsletter.count({ createdAt >= monthStart })
blogPublishedMonth, // blogPost.count({ publishedAt >= monthStart })
socialPublishedMonth, // socialPost.count({ status:"published", publishedAt >= monthStart })
newsletterSentMonth, // emailNewsletter.count({ status:"sent", sentAt >= monthStart })
socialPlatformHealth, // socialPost.groupBy(platform, status) published/failed last 30d
blogForChart, // blogPost.findMany({ createdAt >= last30d, select: createdAt })
socialForChart, // socialPost.findMany({ createdAt >= last30d, select: createdAt })
newsletterForChart, // emailNewsletter.findMany({ createdAt >= last30d, select: createdAt })
] = await Promise.all([...]);Key Status Values
BlogPost: dm_review → dm_approved → client_review → client_approved → published (or rejected)
SocialPost: dm_review → dm_approved → client_review → client_approved → active → scheduled → publishing → published / publish_failed (or rejected)
EmailNewsletter: draft → dm_review → approved → sending → sent (or failed); no client_review stage
Screen M1g — Channel Health (/dashboards/channel-health) [Live Apr 2026]
Purpose: Ops view of connected channel coverage across all tenants — broken connections, low health scores, and channel type coverage at a glance. Supports per-tenant filtering.
File: apps/manage/src/app/(manage)/dashboards/channel-health/page.tsx (async server component, force-dynamic)
Nav: Seventh child of Dashboards NavGroup (Signal icon)
Sections
Overview KPIs (5 cards):
| Card | Value | Color |
|---|---|---|
| Connected | isConnected = true | green if > 0 |
| Disconnected | isConnected = false AND lastConnectedOn != null (was connected, now broken) | danger if > 0 |
| Never Connected | isConnected = false AND lastConnectedOn = null | neutral |
| At-Risk Channels | latest healthScore < 50 (channels with a completed insight) | danger if > 0 |
| Avg Health Score | mean of all latest health scores | green ≥ 70, amber ≥ 50, red < 50 |
When not filtering by tenant: show additional “Tenants with No Connected Channel” card (active tenants with zero isConnected channels).
Coverage by Channel Type (table):
Derived from allChannels grouped by type in JS.
| Column | Source |
|---|---|
| Channel Type | formatted type string |
| Total | count of channels of this type |
| Connected | count where isConnected = true |
| With Insights | count with a completed insight |
| Avg Health Score | mean healthScore for this type |
Disconnected Channels (table — only if any exist):
isConnected = false AND lastConnectedOn != null, sorted by lastConnectedOn asc (longest disconnected first). Capped at 20.
Columns: Tenant | Channel | Type | Last Connected | Days Disconnected
Low Health Score Channels (table — only if any exist):
Channels whose latest completed insight has healthScore < 50, sorted by score asc.
Columns: Tenant | Channel | Type | Health Score | Last Insight
DB Queries (3 parallel)
const [
allTenants, // tenant.findMany({ status: in [active, onboarding] }) — dropdown + coverage calc
allChannels, // connectedChannel.findMany({ tenantId: tId }) + tenant.name + tenant.status
latestInsights, // channelInsight.findMany({ status:"done", tenantId:tId, distinct:["connectedChannelId"], orderBy:{completedAt:"desc"} })
] = await Promise.all([...]);All JS derivations from two in-memory arrays — no extra queries.
healthScore color rules: ≥ 70 green · ≥ 50 amber · < 50 red
Screen M1h — Goal & Strategy Performance (/dashboards/goal-strategy) [Live Apr 2026]
Purpose: Exec and delivery view — are tenants progressing through planning, and are their goals being tracked and achieved? Supports per-tenant filtering.
File: apps/manage/src/app/(manage)/dashboards/goal-strategy/page.tsx (async server component, force-dynamic)
Nav: Eighth child of Dashboards NavGroup (Target icon)
Sections
Overview KPIs (5 cards):
| Card | Value | Color |
|---|---|---|
| Active Plans | deliverablePlan.status = "approved" | green if > 0 |
| Plans in Review | status in ["pending_review", "dm_approved"] | warn if > 0 |
| Active Goals | goal.status = "active" | neutral |
| Goals Being Tracked | goals with at least 1 non-baseline GoalSnapshot | neutral |
| Tenants with Approved Plan | distinct tenantIds across approved plans | green if > 0 |
When not filtering by tenant: also show “Tenants Without Approved Plan” count (active tenants with no approved plan — red if > 0).
Plan Pipeline (table):
| Status | Count |
| pending_review | N |
| dm_approved | N |
| approved | N |
| archived | N |Goal Progress Distribution (table):
For goals in approved plans that have at least one snapshot, compute metricStatus in JS:
function computeMetricStatus(current, baseline, target): string {
if (!target || !baseline) return "pending";
const progress = (current - baseline) / (target - baseline);
if (progress >= 1.0) return "achieved";
if (progress >= 0.75) return "on_track";
if (progress >= 0.5) return "tracking";
if (progress >= 0.25) return "behind";
return "at_risk";
}Displays a count row per status: achieved (green) · on_track (emerald) · tracking (blue) · behind (amber) · at_risk (red) · pending (gray).
Strategy Status (table):
strategy.groupBy(status)._count — shows how many tenants have a draft vs approved strategy.
Tenants Without Approved Plan (table — hidden when filtering by tenant, capped at 20):
Active tenants with no deliverablePlan at status = "approved". Sorted by createdAt asc (oldest first). Columns: Tenant | Joined | Days Since Joining | Link.
DB Queries (6 parallel)
const [
allTenants, // tenant.findMany({ status: in [active, onboarding] }) — dropdown + coverage
plansByStatus, // deliverablePlan.groupBy(status)._count — pipeline state
approvedPlanData, // deliverablePlan.findMany({ status:"approved", select:{tenantId} }) — tenant coverage
strategiesByStatus, // strategy.groupBy(status)._count
activeGoals, // goal.findMany({ status:"active" }) — select id, tenantId, targetNumericValue, baselineValue + plan status
latestGoalSnapshots, // goalSnapshot.findMany({ isBaseline:false, distinct:["goalId"], orderBy:{snapshotDate:"desc"} })
] = await Promise.all([...]);metricStatus color rules: achieved (emerald) · on_track (green) · tracking (blue) · behind (amber) · at_risk / no-snapshot (red) · pending (gray)
Screen M2 — Tenants (/tenants) [Live]
Purpose: Full list of all tenants. Currently the landing page after login.
Header
- Heading: “Tenants”
- Stat cards: Total · Active · Onboarding · Inactive (with counts)
- “Deleted Tenants” link button (→
/tenants/deleted) — neutral style, left of “Create Tenant” - ”+ New” button (→
/tenants/create) - Filter panel toggle
Table columns
| Column | Notes |
|---|---|
| Tenant Name | Avatar initials + name |
| Status | Active / Onboarding / Inactive badge |
| Source | Admin (manually created) · register (self-service) · Self |
| Country / Region | |
| Created At | |
| Actions | ... menu |
Row actions (...)
- View — navigate to Tenant Detail (M3)
- Suspend — confirmation dialog; blocks tenant access
- Change plan — plan change modal
- Impersonate — opens Dashboard as that tenant user for support debugging; action written to
audit_logs
Filter panel
Filters: Status · Source · Date range (created).
Screen M2d — Deleted Tenants (/tenants/deleted) [Live]
Purpose: Audit trail of all tenant deletion jobs — in-progress and completed.
Layout
"use client" page with IntersectionObserver infinite scroll (no page-number pagination).
Table columns
| Column | Notes |
|---|---|
| Tenant | Name |
| Requested By | Name of super admin who triggered deletion |
| Status | Colour badge: pending (yellow) · running (blue) · completed (green) · completed_with_errors (orange) · failed (red) |
| Progress | X / 9 steps completed counter |
| Requested | ISO date of the deletion request |
| Completed | ISO date of completion, or — |
Row click → /tenants/deleted/[deletionId] (M2d-detail).
Proxy route: GET /api/admin/tenant-deletions → GET /admin/v1/tenant-deletions?limit=30&cursor=.
Screen M2d-detail — Deletion Detail (/tenants/deleted/[deletionId]) [Live]
Purpose: Step-by-step log of a single tenant deletion job.
Server-rendered page; data fetched server-side via the API.
Header card
- Tenant name (h1) + Tenant ID in mono
- Status badge + “Run Cleanup” button (shown only when
status === "failed" | "completed_with_errors") - 4 info tiles: Requested By · Requested date · Duration · Job ID (last 8 chars)
- Red error banner if
errorMessageis set
Records Snapshot section
Grid of tiles (count + label) showing the record state at the time deletion was triggered. Keys: blogPosts, socialPosts, keywords, agentRuns, contacts, leads, reports, etc.
Deletion Steps table
| Column | Notes |
|---|---|
| # | Step icon: ○ pending · ◌ running · ✓ completed · ✗ failed · — skipped |
| Step | Label (bold) + step key below in mono |
| Status | Colour badge |
| Records | Count of records affected (or —) |
| Duration | Xms or X.Xs |
| Detail | Human-readable result message (e.g. “3 jobs removed”, “42 files deleted”) |
Run Cleanup button (DeletionCleanupButton.tsx): "use client" component. POSTs to /api/admin/tenant-deletions/[id]/cleanup, then calls router.refresh(). Only visible for failed or completed_with_errors status. Re-runs all 9 steps from scratch (deletes old step rows, creates fresh ones, re-enqueues job).
Proxy routes:
GET /api/admin/tenant-deletions/[id]→GET /admin/v1/tenant-deletions/:idPOST /api/admin/tenant-deletions/[id]/cleanup→POST /admin/v1/tenant-deletions/:id/cleanup
Screen M3 — Tenant Detail (/tenants/[tenantId]) [Live]
Purpose: Full visibility and control for a single tenant.
URL pattern: /tenants/[tenantId] (active tab stored in URL hash, e.g. #invoices)
Layout: Vertical sidebar nav on the left (w-44) + content panel (flex-1) — 19 tabs total.
Tabs
| # | Tab key | Content |
|---|---|---|
| 1 | progress | Month Progress panel |
| 2 | pipeline | Pipeline status panel |
| 3 | context | Read-only client context viewer; version picker (when >1 version); timeline + details sidebar; PDF download. Fetched from GET /api/admin/tenants/[tenantId]/context |
| 4 | strategy | Read-only strategy viewer; same layout as context with purple gradient. Fetched from GET /api/admin/tenants/[tenantId]/strategy |
| 5 | strategy_config | Per-tenant strategy writer input toggles. Fetched from GET/PUT /api/admin/tenants/[tenantId]/strategy-config |
| 6 | deliverable_config | Per-tenant activity planner overrides (max activities, require approval). Fetched from GET/PUT /api/admin/tenants/[tenantId]/deliverable-config |
| 7 | subscription_settings | Multi-row subscriptions table; per-row Edit, Lock/Unlock, Cancel; ”+ Add Subscription” button |
| 8 | invoices | Paginated invoice list (25/page) for this tenant; click row → /invoices/[id]; “Raise Invoice” slide-over; fetched from GET /api/admin/tenants/[tenantId]/invoices |
| 9 | usage | Credits & LLM tab — credit balance + adjust form + LLM spend breakdown + ledger table |
| 10 | activities | Paginated activity log (50/page) with Label, Type, Status, Agent Queue, Due Date, Created columns; fetched from GET /api/admin/activities?tenantId= |
| 11 | goals | Summary stat cards (Total/Ahead/On Track/Behind) + goal cards with progress bars; inline edit on hover; fetched from GET /api/dm/goals?tenantId= |
| 12 | users | All members of this tenant (via TenantMember); ”+ Add User” modal (Existing User search or New User form); static prop from server component |
| 13 | deliverables | Period picker + expandable deliverable type rows with item grid; fetched from GET /api/dm/deliverables?tenantId=&period=YYYY-MM |
| 14 | keywords | Infinite-scroll keyword list; fetched from GET /api/admin/tenant-keywords |
| 15 | keyword_groups | Infinite-scroll keyword groups; fetched from GET /api/admin/tenant-keyword-groups |
| 16 | reports | Infinite-scroll reports list; fetched from GET /api/admin/tenant-reports |
| 17 | audit_logs | Paginated audit trail (50/page); fetched from GET /api/admin/audit-logs?tenantId= |
| 18 | settings | General Settings form — Company (name, website, address, industry, country, timezone, currency) + Point of Contact + Automation (backlink auto-send toggle) |
| 19 | context_settings | Per-tenant context-file-writer input toggles — see Context Settings tab detail below |
| 20 | design_intelligence | Design Intelligence — 6 per-feature AI layer toggles; auto-saves on each toggle |
Tenant header
- Status badge — clickable dropdown (via
TenantStatusChanger) allowing transitions: onboarding→[suspended, cancelled]; active→[suspended, cancelled]; suspended→[active, cancelled]; cancelled→[active, suspended]. Status changes are written to audit log (tenant.status_changed). - Locked Out badge — shown if any subscription is locked out.
- Activate button — shown only when status is
onboarding; triggers the activation flow: sets tenant toactive, stampsbillingActivatedOn/startDateon the subscription (billing cycles start from this date), and writes atenant.activatedaudit log entry. No invoice is created here — the first invoice was already raised at tenant creation. - Delete Tenant button (
DeleteTenantButton.tsx) — red-bordered button in the top-right of the header card. Disabled (shows “Deleting…”) whentenant.status === "deleting". Click opens a confirmation modal requiring the user to type the exact tenant name. On confirm, POSTs to/api/admin/tenants/[tenantId]/delete, which triggers the 9-step background deletion job (see Screen M2d-detail). Redirects to/tenants/deleted/[deletionId]on success. The deletion is irreversible — all DB records, files, vectors, and search index documents are removed.
Subscriptions tab detail
Table columns: Name / Plan | Region | Price | Status | Start Date | Next Billing | Actions
Per-row actions:
- Edit — opens a right slide-over form (Name, Plan picker, Pay without TDS, Payment due days); writes
subscription.updatedaudit log - Lock — inline lock confirm row with optional reason field; writes
subscription.lockedaudit log - Unlock — confirm dialog; writes
subscription.unlockedaudit log - Cancel — confirm dialog; sets status to
cancelled; writessubscription.cancelledaudit log
Add Subscription — right slide-over with Name (required, e.g. “SAAS”, “Hosting”, “Managed Services”), Plan picker, Pay without TDS, Payment due days. Writes subscription.created audit log.
A tenant can have multiple active subscriptions simultaneously (e.g. SAAS + Hosting). Invoices can be raised against any specific active subscription.
Billing lifecycle: The first invoice is created at tenant creation (status
pending, duenow + paymentDueDays). The tenant stays inonboardinguntil manually activated via the Activate button. On activation,billingActivatedOnandstartDateare set on the subscription — the billing server picks these up to generate subsequent invoices from the next cycle onwards.
General Settings tab detail
Company section fields: Company Name (required) · Website · Address (multiline textarea) · Industry · Country · Timezone · Currency. Point of Contact section fields: Name · Email · Phone.
All four pickers (Industry, Country, Timezone, Currency) use a custom SearchableSelect component — click to open dropdown with inline search input, type to filter, click to select. Outside-click closes.
Automation section (added April 2026):
- Backlink Auto-Send — toggle switch (
backlinkAutoSendboolean). When ON, outreach emails drafted by the backlink-outreach-writer agent are sent automatically without requiring DM review. Default: OFF (DM reviews first).
Underlying Tenant model fields: name, website, address, pocName, pocEmail, pocPhone, timezone, country, currency, industry, backlinkAutoSend. (pocPhone, country, currency added April 2026; backlinkAutoSend added April 2026.)
API: PATCH /admin/v1/tenants/:tenantId — sparse update (only provided fields are changed). Server action: updateTenantSettings in actions.ts.
Context Settings tab detail
Controls which inputs the AI setup chain uses when building the tenant’s Client Context File. All four inputs default to true for every tenant. Changes are non-destructive — disabling an input does not delete data; it affects only the next generation run.
Stored as: Tenant.contextConfig Json? — a TenantContextConfig object. Parsed via resolveContextConfig() in setup.worker.ts, which fills in true defaults for any missing keys.
API: PATCH /admin/v1/tenants/:tenantId with { contextConfig: { ... } }. Server action: updateTenantSettings in actions.ts (passes Prisma.DbNull when null to satisfy Prisma’s nullable JSON constraint).
| Toggle | Field | Default | Effect when disabled |
|---|---|---|---|
| Live website crawl | enableWebCrawl | true | Client-researcher uses only the RAG knowledge base; no live crawling. Use for pre-launch tenants, JS-heavy sites, or clients with no public website. |
| Competitor research | enableCompetitorResearch | true | Competitor-researcher step is skipped entirely. Context file Competitive Landscape section will note that competitor research is not configured. |
| Brand voice section | enableBrandVoiceSection | true | Brand voice block is not appended to the context file after generation. Use when the tenant has not yet completed brand voice setup. |
| Key pages section | enableKeyPagesSection | true | Published blog posts and landing pages are not appended to the context file. Use for early-stage clients with no published content. |
Help topic: context-settings — opened via <HelpTrigger slug="context-settings" /> in ContextSettingsTab.tsx.
Goals tab detail — inline editing
Super admins can edit any goal directly without triggering approval resets. Hover a goal card → pencil icon appears → click to expand inline edit form with fields: Title · Target Value · Metric · Timeframe (months) · Channel · Deliverable Types (comma-separated). Save calls PATCH /admin/v1/goals/:goalId via updateGoal server action; local state updates optimistically.
Users tab detail — Add User modal
”+ Add User” button opens a modal with two modes:
- Existing User — searchable list of all platform users fetched from
GET /api/admin/users; select a user and role, callsassignExistingUserserver action (PATCH /admin/v1/users/:userIdwith{ tenantId }). - New User — form with Name / Email / Password / Role; calls
createUserForTenantserver action (POST /admin/v1/users).
Data source: The users list is sourced from
TenantMember, notUser.tenantId. DM reviewers and other cross-tenant users only appear if they have aTenantMemberrecord for this tenant.User.tenantIdreflects their primary/registration tenant and must never be used for membership queries.
Context / Strategy tabs detail
Both tabs are read-only (no approve or revise actions). Layout mirrors dashboard context/strategy pages:
- Hero card with gradient (blue = context, purple = strategy) and MarkdownRenderer content
- Version picker buttons (shown only when >1 version exists)
- Right sidebar with two sub-tabs: Timeline (log entries with actor + message) and Details (version history list)
- Download PDF button — uses
markedto render markdown → custom branded HTML →window.open()+window.print()
Proxy routes: GET /api/admin/tenants/[tenantId]/context and GET /api/admin/tenants/[tenantId]/strategy.
Upstream API: GET /admin/v1/tenants/:tenantId/context and GET /admin/v1/tenants/:tenantId/strategy — include logs and versions via Prisma include.
Strategy Config tab
[Live Apr 2026] Controls which data sources are fed to the strategy writer when generating or revising a strategy for this tenant. All flags default to ON — disable only when the specific input is known to be inaccurate or irrelevant.
Changes take effect on the next strategy run (generate or revision). They do not retroactively affect existing strategy versions.
| Toggle | What it controls | When to turn off |
|---|---|---|
| Baseline performance data | Injects manually entered metrics (monthly visitors, organic clicks, ad spend per platform, social followers) into the prompt | The client entered rough estimates or placeholder numbers during onboarding that would skew the strategy’s goal-setting |
| Knowledge base: brand docs | Runs a RAG search against brand voice, messaging, target audience, and product documents | New tenant with no uploaded brand documents — empty RAG results add noise |
| Knowledge base: competitor research | Runs a RAG search against competitor analysis and market positioning documents | The client is sensitive about competitor mentions appearing in their strategy document |
| Connected channel context | Fetches all OAuth-connected channels (isConnected: true) and tells Claude to prioritise them; also instructs Claude to flag any recommended channels that aren’t connected yet | Channels haven’t been connected yet but you want to run the strategy now — prevents Claude from noting missing connections as warnings |
Data flow: StrategyConfigTab.tsx → PUT /api/admin/tenants/[tenantId]/strategy-config proxy → PUT /admin/v1/tenants/:tenantId/strategy-config Fastify route → upserts TenantStrategyConfig row → strategy-writer worker reads flags at job start.
Where to find it: Manage → Tenants → [Tenant name] → Strategy Config (in the left tab nav, below Strategy).
Help topic: strategy-config — wired via <HelpTrigger slug="strategy-config" /> in StrategyConfigTab.tsx; covers all 4 toggles, their impact, and when to change each setting. Defined in apps/manage/src/app/(manage)/help/_data/index.ts.
Deliverable Config tab
[Live May 2026] Per-tenant overrides for the activity planner. Fields left blank inherit the global value from Manage → System → Deliverable Settings (which in turn falls back to the code default).
| Field | DB column | Behaviour when set | Behaviour when empty |
|---|---|---|---|
| Activity pipeline approval | require_activity_approval | Overrides global requireActivityApproval for this tenant only | Inherits global PlatformSetting (default true) |
| Max activities per period | activity_planner_max_activities | Caps how many activities the planner may generate for this tenant | Inherits global activityPlannerMaxActivities (default 300) |
Resolution chain (both fields): TenantDeliverableConfig value → PlatformSetting global value → code default (300 / true).
Data flow: DeliverableConfigTab.tsx → PUT /api/admin/tenants/[tenantId]/deliverable-config proxy → PUT /admin/v1/tenants/:tenantId/deliverable-config Fastify route → upserts TenantDeliverableConfig row → activity planner worker reads at job start.
Where to find it: Manage → Tenants → [Tenant name] → Deliverable Config (in the left tab nav, below Strategy Config).
DB model: TenantDeliverableConfig — tenant_deliverable_config table; 1:1 with Tenant; nullable fields; onDelete: Cascade.
Planned tabs to add [To Build]
- Config — plan, feature flags, allowed LLM providers, spending limits, deployment mode
- Agents — agent configs for this tenant; inherits from master (M5) with overrides highlighted
- Knowledge Base — dataset summary per tenant: file count, chunk count, embedding model, status; trigger re-index / clear
Knowledge Base tab detail (planned)
| Dataset | Files | Chunks | Embedding Model | Status |
|---|---|---|---|---|
| Client Documents | 3 | 29 | text-embed-3-small | ✅ |
| Website Content | 142 pages | 1,840 | text-embed-3-small | ✅ weekly crawl |
| Published Content | 24 | 312 | text-embed-3-small | ✅ auto |
| Competitor Research | 18 | 210 | nomic-embed-text | ✅ local |
Screen M4 — Global Invoices (/invoices) [Live]
Purpose: Invoice list across all tenants platform-wide.
Stats bar
Four stat cards at the top: Total Invoices · Pending · Paid · Overdue (counts from current filtered set).
Table columns
| Column | Notes |
|---|---|
| Name | ”TenantName – Mon YYYY” (period-based). Invoice number in mono below. Clicking navigates to detail page. |
| Net Payable | Formatted currency amount |
| Due Date | Formatted date |
| Status | Pill badge with dot (draft / pending / paid / overdue / void) |
| Billed On | Invoice issued date |
| Type | ”Subscription” or “One-time” |
| Actions | Eye icon (→ detail page) · Edit icon (→ status/notes slide-over) |
Search: by invoice number or tenant name. Filters: Status dropdown · Type dropdown.
Create Invoice button
”+ Create Invoice” button navigates to the dedicated Invoice Create page (M4c).
Screen M4c — Invoice Create (/invoices/create) [Live]
Purpose: Full-page form to create a new manual invoice for any tenant.
Breadcrumb: Invoices / Create Invoice
Sections
General Details
| Field | Notes |
|---|---|
| Tenant | Required dropdown; auto-fills subscription billing info on selection |
| Currency | Auto-filled from subscription (default INR) |
| Issued Date | Defaults to today |
| Due Date | Defaults to today + paymentDueDays from subscription |
| Period Start / End | Date pickers |
| Tax applicable? | Toggle; when on, shows Tax Type (Inclusive / Exclusive) |
| Notes | Optional textarea |
Billing Address — pre-filled from subscription billing fields if set; editable inline. Name · Address · City · State · Country · Postal.
Line Items — same Add Item modal as M4b (Pre-defined / Custom with Qty, Unit Price, SGST %, CGST %, Discount fields).
- Live summary: Subtotal → GST → Gross Amount → TDS → Net Payable
- Amounts entered in major units (₹), stored in minor units (paise × 100)
Subscription picker: When the selected tenant has multiple active subscriptions, a subscription dropdown is shown in General Details so the invoice can be associated with the correct subscription.
Submit: “Create Invoice” button — validates tenant + billing fields + ≥1 line item, generates invoice number (INV-YYYY-MM-XXXX, zero-padded sequence), creates DB record, redirects to M4a (detail page).
Validation errors appear inline. “This tenant has no subscription” shown if selected tenant is not yet active.
Screen M4a — Invoice Detail (/invoices/[invoiceId]) [Live]
Purpose: Full document-style view of a single invoice with timeline and reminder history.
Layout
Two-column: left = invoice document card; right = Details info + Timeline.
Breadcrumb: ← Invoices / INV-XXXX / Details
Top bar: Status pill · Print / PDF button · Reminders button (badge with count) · Edit Invoice link (→ M4b) · Cancel Invoice button (red, hidden if already void/paid).
Outstanding banner: For pending/overdue invoices — shows net payable amount + days remaining/overdue.
Invoice document card
- Invoice number heading + status badge
- Tenant name + billing period line
- Bill To section: billing address from subscription
- Plans table: Item / Amount / Tax / Total columns; line items rendered as rows
- Tax summary (right-aligned): Total → GST (%) → Gross Total → TDS (%) → Net Payable
- Notes (if set)
Details card (right)
Offering / Plan · Region · Issued date · Due date · Paid date (if paid).
Timeline card (right, data-testid="invoice-timeline")
Chronological events: Invoice created · Invoice issued · Payment due · Marked overdue (if applicable) · Payment received. Color-coded dots (green = paid, red = overdue, amber = due).
Print / PDF
“Print / PDF” button calls window.print(). Sidebar, top bar, outstanding banner, and right panel are hidden via print:hidden Tailwind classes. Invoice document card renders full-width with no borders/shadows for a clean PDF.
Cancel Invoice
“Cancel Invoice” button (shown only if status is not void or paid). Clicking opens a confirmation dialog:
- “This will permanently void the invoice. This cannot be undone.”
- “Yes, Cancel Invoice” / “Keep Invoice” buttons.
- On confirm: sets status to
void, writesinvoice.cancelledaudit log entry.
Reminders modal
Opened via “Reminders” button. Shows:
- Last Reminder card: date + time of most recent reminder sent
- Next Reminder card: scheduled next reminder date (or status message)
- Reminder History list: all reminders with type, channel, sent time, relative timestamp
- Send Now button: manually triggers a reminder email immediately (not shown if paid/void)
Screen M4b — Invoice Edit (/invoices/[invoiceId]/edit) [Live]
Purpose: Full edit form for an invoice — dates, billing address, line items, tax settings.
Breadcrumb: Invoices / INV-XXXX / Edit
Sections
General Details
| Field | Notes |
|---|---|
| Invoice Number | Read-only |
| Status | Dropdown: draft / pending / paid / overdue / void |
| Issued Date | Date picker |
| Due Date | Date picker |
| Period Start / End | Date pickers |
| Tax (GST) % | Number input |
| TDS % | Number input |
| Notes | Textarea |
Billing Address — updates subscription billing fields: Name · Address · City · State · Country · Postal.
Line Items — table with Description / Qty / Unit Price / Amount rows.
- “Add Item” button opens modal.
- Each row has a × remove button.
- Live amount summary (Subtotal → GST → Gross → TDS → Net Payable) updates as items change.
Add Item modal Toggle: Pre-defined (list of common item types) | Custom (free-text description). Fields: Qty · Unit Price · SGST % · CGST %.
Save Changes button: recalculates all amounts and redirects to detail page.
Screen M4p — Plans (/plans) [Live]
Purpose: List and manage all billing plans across offerings and regions.
Header
- Stat cards: Total Plans · Active Plans · Custom Plans · Total Tenants
- Buttons: Offerings (→
/plans/offerings) · Regions (→/plans/regions) · + New Plan (→/plans/new) - Filters: Offering dropdown · Region dropdown · “Custom plans only” checkbox
Table columns
| Column | Notes |
|---|---|
| Plan | Avatar initial + name; green if Custom offering, violet otherwise |
| Offering | Offering name |
| Region | Region name |
| Price | Formatted in major currency units + /mo or /yr |
| Tier | T{n} {label} badge (T0 Starter / T1 Pro / T2 Agency / T3 Enterprise) |
| Trial | Trial days or ”—“ |
| Tenants | Count with user icon |
| Status | Active / Inactive badge |
Row click navigates to plan detail.
Screen M4p-detail — Plan Detail (/plans/[planId]) [Live]
Purpose: Full details for a single billing plan including tenants on that plan.
Header
- Avatar initial + plan name + Custom badge (if applicable) + Active/Inactive badge + Tier badge
- Price in major units + billing cycle (right-aligned)
- Edit button →
/plans/[planId]/edit - Delete button — two-click confirm; disabled (with tooltip) if any tenants are subscribed
Info cards
Offering · Region · Trial days · Tenant count
Features section
Rendered as a grid of key/value pairs from plan.features JSON.
Tenants table
All tenants currently subscribed to this plan: Tenant name (link to detail) · Status · Subscription status badge · Start Date · Next Billing · View link.
Screen M4p-new — New Plan (/plans/new) [Live]
Purpose: Create a new billing plan.
Fields
| Field | Notes |
|---|---|
| Offering | Dropdown of all offerings |
| Region | Dropdown of all regions |
| Name | Text input |
| Description | Optional textarea |
| Price | Number input in major units (paise/fils handled automatically: stored × 100) |
| Billing Cycle | monthly / annual |
| Tier | 0 = Starter / 1 = Professional / 2 = Agency / 3 = Enterprise |
| Trial Days | Number, default 0 |
| Active | Checkbox, default true |
Features section
Structured inputs for common feature keys:
- Max Users, Max Agents (numbers)
- Credits / Month (number — drives monthly credit allocation via
plan.features.creditsPerMonth) - Highlights (dynamic list — add / remove string items)
Screen M4p-edit — Edit Plan (/plans/[planId]/edit) [Live]
Same fields as New Plan. Offering and Region are shown as read-only labels (cannot be changed after creation).
Screen M4p-offerings — Offerings (/plans/offerings) [Live]
Purpose: List and manage plan offerings (e.g. “Starter”, “Professional”, “Custom”).
Table: Name · Description · Plans count · Active status · Edit link · Delete button. Delete is blocked if offering has any plans.
Sub-pages: /plans/offerings/new (create) · /plans/offerings/[offeringId]/edit (edit).
Fields: Name (unique), Description, Active toggle.
Screen M4p-regions — Regions (/plans/regions) [Live]
Purpose: List and manage billing regions (e.g. “India”, “Middle East”).
Table: Code · Name · Currency · Tax% · TDS% · Active status · Edit link · Delete button. Delete is blocked if region has any plans.
Sub-pages: /plans/regions/new (create) · /plans/regions/[regionId]/edit (edit).
Fields: Name (unique), Code (2–5 chars, uppercase, immutable after creation), Currency, Currency Symbol, Tax %, TDS %, Active toggle.
Screen M5 — Global Users (/users) [Live]
Purpose: All human users across the entire platform.
Table of all users across all tenants with roles, tenant association, and status.
Screen M6 — Global Deliverables (/deliverables) [Live]
Purpose: Deliverable management across all tenants.
Table of all deliverables platform-wide.
Screen M7 — Master Agent Configs (/agents) [Live]
Purpose: Platform-wide agent configurations and run analytics. The page has two tabs: All Agents (config list) and Analytics (platform-wide run metrics).
All Agents tab (/agents):
- Stat cards: Total Agents · Free Tier · Pro Tier · Active
- Grouped by category: Setup / Strategy / Orchestration / SEO / Content / Reporting / Research / Reactive
- Columns: Agent name + description · Model · Plan badge · Status indicator · View link
Agent detail page (/agents/[agentId]):
- Header: name · plan badge · active status
- Active run banner (if agent is currently running — shows tenant + start time)
- Detail cards: Adapter (label + model ID) · Category · Role Identifier · Tenant Configs count
- Activity Overview —
AgentRunStatscomponent: 4 stat cards (Total Runs · Total Time · Total Cost · Avg Duration) + 30-day bar chart. Data fromGET /admin/v1/agents/:agentId/run-stats(cross-tenant aggregate). Bars: violet = completed only, amber = has failures, grey = no activity. Hover tooltip per day. - Mapped Skills section — shows skills currently mapped to this agent; “Add Skill” button opens a modal to search and select from the global skills library; individual skills can be removed with the × button
- System Prompt section (scrollable pre block)
- Run History table (last 10 runs with duration, cost, tools, adapter/model, status)
- “View all runs” link →
/agents/[agentId]/runs - Edit button →
/agents/[agentId]/edit
Agent edit page (/agents/[agentId]/edit):
- Fields: Name · Description · Adapter (dropdown: Claude Code / Codex / Gemini CLI) · Model (dropdown, auto-populated per adapter) · System Prompt · Plan Tier · Active toggle
- Changing adapter auto-resets model to the first model of the new adapter
- Adapter + model saved to
agent_config.adapter+agent_config.modelin DB
Analytics tab (/agents?tab=analytics) [Live]:
- Period selector: Today (1 day) / 7 Days / 30 Days / 90 Days
- Row 1 stat cards: Total Runs · Completed (with success %) · Failed (with running count) · Total Cost
- Row 2 stat cards: Total Time · Avg Duration · Active Agents in period · Avg Cost per Run
- Activity chart: bar chart per day over period (violet = clean, amber = has failures, grey = no activity); hover tooltips
- Adapter Usage breakdown: horizontal bar per adapter (Claude Code / Codex CLI / Gemini CLI) with run count, cost, % share
- Model Usage breakdown: horizontal bar per model with run count, cost, % share (up to 10 models)
- Top Agents table: Agent name + role · Runs · Completed · Failed · Success % (color-coded green/amber/red) · Total Cost · Avg Duration
- Data from
GET /api/admin/analytics/agents?period=→ proxies toGET /admin/v1/analytics/agents?period=
Agent runs page (/agents/[agentId]/runs):
- Filter bar: Status · Tenant search · From/To date · Sort (newest/oldest)
- Paginated table with cursor-based “Load more”: Tenant · Status · Task · Duration · Cost · Tools · Skills (⚡ N count) · Adapter / Model · Started
- URL-synced filter state
- Skills column shows
—for runs predating the skills logging feature - Adapter / Model column shows adapter label (Claude Code / Codex / Gemini CLI) with model ID below in mono;
—for older runs without adapter data
Agent run detail (/agents/[agentId]/runs/[runId]):
- Stat cards: Duration · Cost · Tool Calls
- Input section: task summary · dynamic context · system prompt
- Skills Injected card — chips showing each skill’s name, filename, and category badge; hidden when no skills were logged
- Error banner (if failed)
- Full output in scrollable pre block
Screen M8 — Global Skills (/skills) [Live]
Purpose: Platform-wide skill library. Skill files are written to the agent’s temp skill directory at execution time and passed to Claude Code via --add-dir.
Route: /skills (sidebar entry: Skills)
Skills list page (/skills):
- Stat cards: Total Skills · Active · Archived · Platform Defaults
- Filter bar: search (name/description/filename) · Category · Status · File Type · Sort · Order
- Table columns: Name (with
defaultbadge) · File (.md/.js/.sh badge + filename) · Category · Agents (count) · Status · Updated date · Edit link
Create page (/skills/new):
- Fields: Name · Category · Description · Filename (auto-generated from name) · File Type (.md / .js / .sh) · Content (monospace textarea, 24 rows) · Platform default checkbox
- Filename extension auto-updates when File Type changes
Edit page (/skills/[skillId]/edit):
- Same form pre-filled
- Archive / Restore toggle button (soft delete — sets status to “archived”, does not delete row)
Data model:
skill: id · name · description · filename · fileType · content · category · isDefault · status · createdAt · updatedAt
agent_skill: id · agentConfigId · skillId · createdAt [unique on (agentConfigId, skillId)]Execution flow: At agent job start, createSkillsDir(tenantId, agentRole) queries agent_skill for active skills mapped to that role and writes each file (filename + content) to the temp dir alongside the always-present search_knowledge.js.
Platform-seeded default skills (15 total):
| Skill | Category | Mapped to |
|---|---|---|
| Research Methodology Guide | Research | client-researcher, competitor-researcher, topic-researcher, research-note-writer |
| Competitor Analysis Framework | Research | competitor-researcher |
| Context File Structure | General | context-file-writer |
| Marketing Strategy Framework | Strategy | strategy-writer |
| Deliverable Types Reference | Strategy | deliverable-planner, activity-planner |
| SEO Writing Best Practices | SEO | blog-writer, keyword-researcher, content-brief-writer, landing-page-writer |
| Keyword Research Methodology | SEO | keyword-researcher, content-brief-writer |
| Content Brief Template | SEO | content-brief-writer, blog-writer |
| Social Media Platform Guide | Content | social-post-writer, social-calendar-planner |
| Ad Copy Frameworks | Content | google-ads-writer, meta-ads-writer |
| Email Copywriting Framework | Content | email-writer |
| Landing Page Conversion Principles | Content | landing-page-writer |
| GBP Post Best Practices | Content | gbp-post-writer |
| Reporting Standards Guide | Reporting | report-writer, ads-analyst, anomaly-detector |
| Review Response Guide | General | review-response-writer |
Tenant-specific skills are [To Build] — planned for the tenant Settings screen.
Screen M9 — LLM Providers (/providers) [To Build]
Purpose: Platform-wide LLM provider management and health monitoring.
| Provider | Status | API Key | Total Cost (Month) | Actions |
|---|---|---|---|---|
| Anthropic | ✅ OK | sk-…abc | $892 | Configure |
| OpenAI | ✅ OK | sk-…xyz | $312 | Configure |
| Ollama | ✅ Local | — | $0 | Configure |
Per-provider detail panel:
- Rate limit status (requests/min, tokens/min) — live
- Error rate (last 1h, last 24h)
- Model availability (which models are enabled platform-wide)
- Toggle enable/disable per plan tier (e.g. disable Claude Opus for Free plan tenants)
Screen M10 — Email Templates (/templates/email) [Live]
Purpose: Manage system email templates used for notifications and transactional emails.
Header
- “Templates (51)” — 51 templates in dev
- Filter button · ”+ Add” button
Table columns
| Column | Notes |
|---|---|
| Name | Template key (camelCase, e.g. invoiceDraftStatusMail) |
| Title | Human-readable subject/title |
| Type | Always email |
| Created On | Date |
| Actions | ... menu |
Observed templates (sample)
invoiceDraftStatusMail · clientReviewMail · clientRejectedMail · clientApprovedMail · autoApprovedMail · credentialMail · register · welcomeMailSelf · welcomeMailAdmin · userWelcomeMail · newLeadMail
Interactions
- Click row → right slide-over with template details and body preview
/templates/email/[name]direct URL returns 404 — not yet implemented; use slide-over
Screen M11 — Activity Templates (/templates/activity) [Live — partial]
Purpose: Platform-wide activity templates used to generate recurring activities for tenants.
Current state [Live]
- “Activity Templates (1703)” — flat table
- Columns: Channel · Title · Description · Type · Frequency · Due Days ·
...actions - Filter button · Import button
- Titles and descriptions use
{{VariableName}}placeholder syntax
Planned enhancement [To Build]
Split into two tabs matching the full pipeline architecture:
Pipeline Templates tab
| Template Name | Deliverable Type | Steps | Tenants Using | Actions |
|---|---|---|---|---|
| Standard Blog Post Pipeline | blog_posts | 4 | 14 | Edit / Clone / Archive |
| Standard Backlinks Pipeline | backlinks | 5 | 11 | Edit / Clone / Archive |
| GBP Posts Pipeline | gbp_posts | 4 | 8 | Edit / Clone / Archive |
Visual pipeline editor: Each step as a card — name, assignee type (agent or human), agent role, is approval gate. Drag to reorder. “Save as new version”. “Push to tenants” (confirmation shows affected count).
Recurring Task Templates tab
| Template Name | Trigger | Periodicity | Assignee | Variables | Tenants |
|---|---|---|---|---|---|
| Update GBP “always open” | on_onboarding | one_time | Human | gbp_url | 14/14 |
| Request Google reviews | cron | monthly | Human | gbp_url | 14/14 |
| NAP consistency check | cron | quarterly | SEO Specialist agent | business_name, address | 9/14 |
Template editor fields: Name · description · assignee (agent role or human role) · periodicity · trigger · cron expression with human-readable preview · prompt template with {{variable}} syntax · variable definitions table.
Variable substitution preview: Select a tenant from dropdown to preview with real data.
On save: New tenants get this template automatically. Existing tenants are not updated unless they adopt it (show non-adopting tenant count).
Screen M12 — Request Queue (/request-queue) [Live]
Purpose: Manage incoming blog content creation requests from digital marketers.
Header
- “Request Queue — Manage incoming blog creation requests from digital marketers”
- ”+ Add Request” button
Stat cards
| Card | Count (observed) |
|---|---|
| Pending Requests | 29 |
| My Active | 16 |
| Needs Action | 26 |
| In Review | 12 |
Tabs
All (63) · Pending · In Progress · Needs Action
Filters / view options
- My Assignments toggle
- Filter button · Sort button
- View switcher: List · Grid · Table · Kanban
Request card fields (List view)
| Field | Notes |
|---|---|
| Tenant | Avatar + name |
| Requested By | DM who submitted |
| Title | Blog title (linked) |
| Keywords | Primary keyword chips |
| Request ID | REQ[Date]#[Hash] format |
| Created | Creation date |
| Due Date | Red + “OVERDUE” if past due |
| Blog Month | Target publication month |
| Assigned To | Team member or Blog AI Agent |
| Status badge | DM Review · Pending · In Progress · Needs Action |
Pending / Unassigned cards additionally show a Move to In Progress button.
Pagination: 10/page configurable. 63 requests → 7 pages observed.
Screen M13 — Request Detail (/request-queue/[requestId]) [Live]
Purpose: Full details and actions for a single blog request.
Header bar
← Back to Queue | [Request ID] | [Status badge] | v[N]
Title section
Tenant avatar + name · blog title (h1) · action buttons.
Action buttons
- Copy Details — copies request info to clipboard
- Copy Image Prompt — copies image generation prompt
- Blog AI Agent (dropdown):
- Reassign to Team Member: Assign to Me · Blog AI Agent · [named team members]
- Move to On Hold
- Move to Pending
Request Information section
| Field | Notes |
|---|---|
| Primary Keywords | Chip(s) |
| Required Date | Red + “OVERDUE” if past due |
| Blog Month | Target month |
| Requested By | DM name |
| Assigned To | Agent or team member |
| Created | Relative time |
| Secondary Keywords | Additional keyword chips |
Feedback & Comments section
Threaded comments. Empty state: “No comments found”.
Screen M14 — All Blogs (/all-blogs) [Live]
Purpose: View and manage all generated blog posts across all lifecycle stages.
Header
- “All Blogs — View and manage all blog posts across all stages”
- Filter button · Sort button
Tabs
All (18) · DM Review · Client Review · Approved · Published · On Hold
Table columns
| Column | Notes |
|---|---|
| Title / Keyword | Blog title + primary keyword chip |
| Client | Tenant avatar + name |
| Submitted for Review | Date + time |
| Status | DM Review · Client Review · Approved · Published · On Hold |
| Version | v1, v2, v3… (increments on revision) |
| Actions | Eye icon → slide-over |
Blog detail slide-over (row click or eye icon)
Right panel: Status · Version · Submitted for Review · Last Updated · Requested By · Assigned To · Keywords · Submission History timeline.
Screen M15 — System Health (/system) [To Build]
Purpose: Infrastructure and operational health monitoring.
- Service status cards: Next.js (Dashboard / DM Portal / Manage) · Fastify API · PostgreSQL · MongoDB · Redis · Ollama — green/red with uptime %
- Queue depths: BullMQ queue size per agent type · currently processing · failed jobs in DLQ
- Database metrics: active connections · slow queries (> 500ms) · replication lag
- Recent errors: last 20 system-level errors with service, message, timestamp
- Active SSE connections: live client count per app
Quick actions:
- Drain / pause a specific BullMQ queue
- Clear DLQ (with confirmation)
- Force re-sync token refresh for all tenants
- Trigger manual DataForSEO backlink check run
Screen M16 — Backlink Directories (/directories) [Live]
Purpose: Platform-wide directory database management. Superadmins curate the list of business directories used by the opportunity-matcher agent when finding submission opportunities for tenants.
List page (/directories)
Stats row (5 tiles): Total · Active · Free · Dofollow · Agent Ready (violet — count of isAutoSubmittable = true entries).
Filters:
- Search input — client-side; matches name, category, subcategory.
- “Agent Ready” toggle button — filters list to
isAutoSubmittable = trueonly (violet when active).
Table columns: Name · Category/Subcategory · DR · Link type · Difficulty · Regions · Free/Paid · Active · →
Name cell: Directory name + optional violet Bot badge (shown when isAutoSubmittable = true; title tooltip “Agent can auto-submit”) + external Globe link icon.
Row click → /directories/[id].
Detail / Edit page (/directories/[id])
Basic Info card fields: Name · URL · Category · Subcategory · Domain Rating · Link Type · Is Free · Is Paid · Paid Price (USD) · Regions · Difficulty · Estimated Minutes · Is Active · Is Auto-Submittable (checkbox) · Submission Notes (textarea — admin notes on CAPTCHA/login requirements for this directory).
Steps card: Ordered list of steps (title, description, URL, estimated minutes) for manual submission; also used by the directory-submitter agent as the navigation hint for step 1.
API: PATCH /api/admin/directories/[id] → PATCH /admin/v1/backlink-directories/:id.
Create page (/directories/new)
Same fields as detail page. POST /api/admin/directories → POST /admin/v1/backlink-directories.
isAutoSubmittable behaviour
When isAutoSubmittable = true, the directory-submitter Playwright agent will attempt to autonomously fill and submit the listing form when a DM sets an opportunity backlink to in_progress. The agent detects CAPTCHA, login walls, and OTP fields and marks submissionStatus = "failed" with a reason if it can’t proceed — the human then follows the manual steps[] guide.
Help Center (/help + /help/*) [Live]
Purpose: In-app documentation for all Manage portal features, accessible to super admins.
Index page (/help):
- Directory of 15 help articles grouped by category (Tenants & Users · AI Agents & Skills · Billing · Notifications & Templates · Content Management · System)
- Client-side full-text search across all article content
Topic pages:
| Route | Topic |
|---|---|
/help/tenants | Tenants — lifecycle, detail tabs, creating tenants |
/help/users | Users — roles, inviting admins and DMs |
/help/agents | Agents — registry, categories, runs, analytics tab |
/help/skills | Skills — types, assigning to agents |
/help/invoices | Invoices — lifecycle, fields |
/help/plans | Plans — subscription tiers, multi-subscription |
/help/usage | Credits & Usage — consumption, balance adjustments |
/help/costs | LLM Costs — model/agent/tenant breakdown |
/help/templates/email | Email Templates — variables, editing |
/help/templates/telegram | Telegram Templates — format, event-driven |
/help/blog-posts | Blog Posts — cross-tenant view, status flow |
/help/social-posts | Social Posts — cross-tenant view, status flow |
/help/push-notifications | Push Notifications — broadcast, FCM |
/help/audit-logs | Audit Logs — fields, search, export |
/help/rag-config | RAG & AI Config — datasets, adapters, re-indexing |
Architecture: Data in apps/manage/src/app/(manage)/help/_data/index.ts; shared HelpPage/HelpCenter components from @leadmetrics/ui.
Screen M17 — Important Days (/system/important-days) [Live]
Purpose: Platform-wide catalogue of public holidays, observances, and cultural occasions used to trigger occasion post suggestions for tenants.
List page (/system/important-days) [Live]
Filters (left sidebar):
- Region dropdown — “All”, or ISO-3166 country code (IN, US, AU, GB, etc.)
- Month dropdown — “All”, Jan – Dec
- Active only toggle
Table columns: Name · Date · Scope (Global / country codes) · Platforms · Active toggle
Row click → opens edit modal (inline edit, not a separate page).
“Add Day” button (top-right) — opens create modal.
Create / Edit modal
Fields:
- Name (text) — required
- Description (textarea) — optional
- Month (1–12) / Day (1–31) — required
- Year — optional; null = recurring annually
- Is Global checkbox — if checked, applies to all tenants regardless of country
- Countries multi-select — ISO-3166 codes (only relevant when not global)
- Platforms checkboxes — default platforms to suggest for this occasion (instagram, linkedin, facebook, google_business_profile, x)
- Active toggle
Seeded occasions
15 occasions pre-loaded in packages/db/src/seed.ts:
| Occasion | Date | Scope |
|---|---|---|
| New Year’s Day | Jan 1 | Global |
| Republic Day | Jan 26 | IN |
| Valentine’s Day | Feb 14 | Global |
| Women’s Day | Mar 8 | Global |
| Earth Day | Apr 22 | Global |
| Labour Day | May 1 | Global |
| Independence Day | Aug 15 | IN |
| Gandhi Jayanti | Oct 2 | IN |
| Children’s Day | Nov 14 | IN |
| Christmas | Dec 25 | Global |
| New Year’s Eve | Dec 31 | Global |
| (+ 4 more India-specific) | varies | IN |
API
All manage routes require super_admin role.
| Method | Route | Description |
|---|---|---|
GET | /admin/v1/important-days | List with optional ?month=&country=&active= filters |
POST | /admin/v1/important-days | Create a new day |
PATCH | /admin/v1/important-days/:id | Update fields |
DELETE | /admin/v1/important-days/:id | Delete a day |
Files
| File | Notes |
|---|---|
apps/manage/src/app/(manage)/system/important-days/page.tsx | Server component, fetches list |
apps/manage/src/app/(manage)/system/important-days/ImportantDaysClient.tsx | Client component, modals, table |
apps/manage/src/app/actions/important-days.ts | Server actions (create/update/delete) |
apps/api/src/routers/admin/important-days.ts | Fastify CRUD routes |
packages/db/prisma/schema.prisma | ImportantDay model |
Screen M18 — Autonomy (/system/deliverable-settings) [Live — May 2026]
Purpose: Global toggles that control which content types require a DM human review step before proceeding to the next pipeline stage. All toggles default to ON (approval required). Disabling a toggle lets the agent pipeline skip straight to the next automated stage without waiting for a human reviewer.
Nav label: “Autonomy” (under System group, Package icon).
Toggles
| Section | PlatformSetting key | Default | Skips to (when OFF) |
|---|---|---|---|
| Activity Pipeline — require DM approval | requireActivityApproval | true | Agents dispatch immediately after planning |
| Activity Pipeline — max activities per period | activityPlannerMaxActivities | 300 | (number cap, not a toggle) |
| Blog Posts | requireBlogApproval | true | client_review |
| Social Posts (FB / IG / LI / GBP) | requireSocialApproval | true | client_review |
| Landing Pages | requireLandingPageApproval | true | client_review |
| Email Newsletters | requireEmailApproval | true | approved |
Behaviour note for social posts: when requireSocialApproval = false, posts skip DM review AND the design step (Social Post Designer) since that step requires a DM-selected channel. The post lands directly in client_review as text-only copy.
Data flow
- Settings stored in
PlatformSettingtable (key-value,valueis Json). - Workers read their key via
db.platformSetting.findUniqueat job execution time — no restart needed. - Default logic in workers:
approvalSetting?.value !== false→ approval ON when no DB row exists.
API
GET /admin/v1/deliverable-settings— returns all 6 keys merged with defaultsPATCH /admin/v1/deliverable-settings— upserts each provided key; writes audit log
Key files
| File | Role |
|---|---|
apps/manage/src/app/(manage)/system/deliverable-settings/page.tsx | Server component, fetches settings |
apps/manage/src/app/(manage)/system/deliverable-settings/DeliverableSettingsClient.tsx | Client, toggle + number input UI |
apps/manage/src/app/actions/deliverable-settings.ts | Server actions (getDeliverableSettings, saveDeliverableSettings) |
apps/api/src/routers/admin/deliverable-settings.ts | Fastify GET + PATCH routes |
packages/agents/src/workers/activity.worker.ts | Reads requireActivityApproval |
packages/agents/src/workers/blog-writer.worker.ts | Reads requireBlogApproval |
packages/agents/src/workers/social-post-writer.worker.ts | Reads requireSocialApproval |
packages/agents/src/workers/gbp-post-writer.worker.ts | Reads requireSocialApproval |
packages/agents/src/workers/landing-page-writer.worker.ts | Reads requireLandingPageApproval |
packages/agents/src/workers/email-writer.worker.ts | Reads requireEmailApproval |
Background agents (not screens)
Two system agents appear in Manage app context but run in the background:
| Agent | Role |
|---|---|
| Blog AI Agent | Generates blog drafts for Request Queue items; shows as assignee in M12/M13 |
| Billing Agent | Auto-creates invoices; shows as actor in invoice audit trail |