add platform analytics route to the dashboard

This commit is contained in:
mantrakp04 2026-06-18 16:53:03 -07:00
parent 75e497f3ec
commit effeacb70a
12 changed files with 1594 additions and 223 deletions

View File

@ -103,7 +103,7 @@
"jiti": "^2.6.1",
"jose": "^6.1.3",
"json-diff": "^1.0.6",
"next": "16.2.7",
"next": "16.2.9",
"nodemailer": "^6.9.10",
"oidc-provider": "^8.5.1",
"openid-client": "5.6.4",

View File

@ -0,0 +1,699 @@
import { Prisma } from "@/generated/prisma/client";
import { getClickhouseAdminClientForMetrics } from "@/lib/clickhouse";
import { ensurePlatformAdmin } from "@/lib/platform-admin";
import { DEFAULT_BRANCH_ID } from "@/lib/tenancies";
import { globalPrismaClient } from "@/prisma-client";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { KnownErrors } from "@hexclave/shared";
import { adaptSchema, clientOrHigherAuthTypeSchema, yupArray, yupNumber, yupObject, yupRecord, yupString } from "@hexclave/shared/dist/schema-fields";
import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
// Platform-wide analytics for the internal (platform team) dashboard. Aggregates
// across EVERY customer project in a handful of grouped queries — never N per-project
// calls. Reachable only from the internal project route, which in this deployment is
// the platform team's private dashboard.
const WINDOW_DAYS = 30;
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const LEADERBOARD_LIMIT = 500;
const INTERNAL_PROJECT_ID = "internal";
const AVG_DAYS_PER_MONTH = 365.25 / 12;
const MRR_SUBSCRIPTION_STATUSES = ["active", "trialing"];
const REVENUE_INVOICE_STATUSES = ["paid", "succeeded"];
function ymd(date: Date): string {
return date.toISOString().split("T")[0];
}
function chDateTime(date: Date): string {
// ClickHouse DateTime params are "YYYY-MM-DDTHH:MM:SS" with no timezone; treated as UTC.
return date.toISOString().slice(0, 19);
}
type CountRow = { projectId: string, c: string | number };
function rowsToMap(rows: CountRow[]): Map<string, number> {
const out = new Map<string, number>();
for (const row of rows) out.set(row.projectId, Number(row.c));
return out;
}
function num(value: unknown): number {
const n = Number(value);
return Number.isFinite(n) ? n : 0;
}
// Normalize a single subscription's chosen recurring price to monthly cents.
// Returns 0 for one-time prices (no interval) or missing/non-USD amounts.
function monthlyRecurringCents(product: unknown, priceId: string | null, quantity: number): number {
if (priceId == null || product == null || typeof product !== "object") return 0;
const prices = (product as { prices?: unknown }).prices;
if (prices == null || typeof prices !== "object") return 0;
const price = (prices as Record<string, unknown>)[priceId];
if (price == null || typeof price !== "object") return 0;
const interval = (price as { interval?: unknown }).interval;
if (!Array.isArray(interval) || interval.length < 2) return 0; // one-time purchase
const count = Number(interval[0]);
const unit = String(interval[1]);
const unitMonths = unit === "day" ? 1 / AVG_DAYS_PER_MONTH
: unit === "week" ? 7 / AVG_DAYS_PER_MONTH
: unit === "month" ? 1
: unit === "year" ? 12
: 0;
const intervalMonths = count * unitMonths;
if (!(intervalMonths > 0)) return 0;
// Amounts are decimal strings per currency (e.g. "9.99"); we sum USD only.
const usd = (price as Record<string, unknown>).USD;
const amount = usd == null ? NaN : Number(usd);
if (!Number.isFinite(amount)) return 0;
return Math.round((amount * 100 * (quantity || 1)) / intervalMonths);
}
const KpiSchema = yupObject({
value: yupNumber().defined(),
prev: yupNumber().nullable().defined(),
}).defined();
const SeriesPointSchema = yupObject({
date: yupString().defined(),
signups: yupNumber().integer().defined(),
active_users: yupNumber().integer().defined(),
page_views: yupNumber().integer().defined(),
visitors: yupNumber().integer().defined(),
revenue_cents: yupNumber().integer().defined(),
}).defined();
const SplitPointsSchema = yupArray(yupObject({
date: yupString().defined(),
activity: yupNumber().defined(),
}).defined()).defined();
const ProjectRowSchema = yupObject({
id: yupString().defined(),
display_name: yupString().defined(),
created_at: yupString().defined(),
total_users: yupNumber().integer().defined(),
verified_users: yupNumber().integer().defined(),
active_users: yupNumber().integer().defined(),
active_users_prev: yupNumber().integer().defined(),
signups: yupNumber().integer().defined(),
signups_prev: yupNumber().integer().defined(),
revenue_cents: yupNumber().integer().defined(),
revenue_cents_prev: yupNumber().integer().defined(),
features: yupArray(yupString().defined()).defined(),
sparkline: yupArray(yupNumber().defined()).defined(),
}).defined();
export const GET = createSmartRouteHandler({
metadata: { hidden: true },
request: yupObject({
auth: yupObject({
type: clientOrHigherAuthTypeSchema.defined(),
tenancy: adaptSchema.defined(),
user: adaptSchema,
project: adaptSchema.defined(),
}),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
generated_at: yupString().defined(),
window_days: yupNumber().integer().defined(),
kpis: yupObject({
active_projects: KpiSchema,
total_users: KpiSchema,
verified_users: KpiSchema,
mau: KpiSchema,
dau_avg: KpiSchema,
stickiness: KpiSchema,
new_signups: KpiSchema,
mrr_cents: KpiSchema,
active_subscriptions: KpiSchema,
email_deliverability_rate: KpiSchema,
}).defined(),
series: yupArray(SeriesPointSchema).defined(),
activity_split: yupObject({
total: SplitPointsSchema,
new: SplitPointsSchema,
retained: SplitPointsSchema,
reactivated: SplitPointsSchema,
}).defined(),
breakdowns: yupObject({
auth_methods: yupArray(yupObject({
method: yupString().defined(),
count: yupNumber().integer().defined(),
}).defined()).defined(),
users_by_status: yupObject({
verified: yupNumber().integer().defined(),
unverified: yupNumber().integer().defined(),
anonymous: yupNumber().integer().defined(),
}).defined(),
users_by_country: yupRecord(yupString().defined(), yupNumber().integer().defined()).defined(),
email: yupObject({
sent: yupNumber().integer().defined(),
delivered: yupNumber().integer().defined(),
bounced: yupNumber().integer().defined(),
error: yupNumber().integer().defined(),
in_progress: yupNumber().integer().defined(),
}).defined(),
dead_click_rate: yupNumber().defined(),
}).defined(),
feature_adoption: yupArray(yupObject({
feature: yupString().defined(),
projects_using: yupNumber().integer().defined(),
}).defined()).defined(),
projects: yupArray(ProjectRowSchema).defined(),
}).defined(),
}),
handler: async (req) => {
if (!req.auth.user) {
throw new KnownErrors.UserAuthenticationRequired();
}
if (req.auth.project.id !== INTERNAL_PROJECT_ID) {
throw new KnownErrors.ExpectedInternalProject();
}
// Being signed into the internal project is not enough — this returns data
// across ALL customer projects, so require membership of the internal project's
// owning team (the platform team).
await ensurePlatformAdmin(req.auth.user);
const now = new Date();
const todayUtc = new Date(now);
todayUtc.setUTCHours(0, 0, 0, 0);
const windowStart = new Date(todayUtc.getTime() - (WINDOW_DAYS - 1) * ONE_DAY_MS); // first day shown
const priorStart = new Date(todayUtc.getTime() - (2 * WINDOW_DAYS - 1) * ONE_DAY_MS);
const untilExclusive = new Date(todayUtc.getTime() + ONE_DAY_MS);
const midParam = chDateTime(windowStart); // boundary between prior and current windows
const sinceParam = chDateTime(windowStart);
const priorSinceParam = chDateTime(priorStart);
const untilParam = chDateTime(untilExclusive);
const branchId = DEFAULT_BRANCH_ID;
// Ordered day axis for the visible window.
const windowDays: string[] = [];
for (let i = 0; i < WINDOW_DAYS; i += 1) {
windowDays.push(ymd(new Date(windowStart.getTime() + i * ONE_DAY_MS)));
}
// All real customer projects (exclude the internal project itself).
const projectRows = await globalPrismaClient.project.findMany({
where: { id: { not: INTERNAL_PROJECT_ID } },
select: { id: true, displayName: true, createdAt: true },
});
const projectInfo = new Map(projectRows.map((p) => [p.id, p]));
if (projectInfo.size === 0) {
return {
statusCode: 200 as const,
bodyType: "json" as const,
body: emptyBody(now),
};
}
const clickhouse = getClickhouseAdminClientForMetrics();
const chQuery = async <T,>(query: string, params: Record<string, unknown>): Promise<T[]> => {
const result = await clickhouse.query({ query, query_params: params, format: "JSONEachRow" });
return await result.json<T>();
};
const userScope = `branch_id = {branchId:String} AND sync_is_deleted = 0`;
const baseParams = { branchId };
const windowParams = { branchId, since: sinceParam, until: untilParam };
const twoWindowParams = { branchId, priorSince: priorSinceParam, mid: midParam, until: untilParam };
let ch: {
dauSeries: Array<{ day: string, c: string | number }>,
pvSeries: Array<{ day: string, pv: string | number, visitors: string | number }>,
signupSeries: Array<{ day: string, c: string | number }>,
mauProjects: Array<{ mauCur: string | number, mauPrev: string | number, projCur: string | number, projPrev: string | number }>,
userCounts: Array<{ total: string | number, totalPrev: string | number, verified: string | number, verifiedPrev: string | number, anonymous: string | number }>,
country: Array<{ country_code: string, c: string | number }>,
deadClicks: Array<{ clicks: string | number, dead: string | number }>,
split: Array<{ day: string, total_count: string, new_count: string, retained_count: string, reactivated_count: string }>,
totalsByProject: CountRow[],
verifiedByProject: CountRow[],
signupsByProject: Array<{ projectId: string, cur: string | number, prev: string | number }>,
activeByProject: Array<{ projectId: string, cur: string | number, prev: string | number }>,
sparkByProject: Array<{ projectId: string, day: string, c: string | number }>,
teamsByProject: CountRow[],
oauthByProject: CountRow[],
emailsByProject: CountRow[],
analyticsByProject: CountRow[],
};
try {
const verifiedSubquery = `
(project_id, id) IN (
SELECT project_id, user_id FROM analytics_internal.contact_channels FINAL
WHERE branch_id = {branchId:String} AND sync_is_deleted = 0 AND type = 'EMAIL' AND is_verified = 1
)`;
const [
dauSeries, pvSeries, signupSeries, mauProjects, userCounts, country, deadClicks, split,
totalsByProject, verifiedByProject, signupsByProject, activeByProject, sparkByProject,
teamsByProject, oauthByProject, emailsByProject, analyticsByProject,
] = await Promise.all([
// Platform daily DAU (active users) over the visible window.
chQuery<{ day: string, c: string | number }>(`
SELECT toDate(event_at) AS day, uniqExact(assumeNotNull(user_id)) AS c
FROM analytics_internal.events
WHERE event_type = '$token-refresh' AND user_id IS NOT NULL
AND event_at >= {since:DateTime} AND event_at < {until:DateTime}
GROUP BY day ORDER BY day ASC
`, windowParams),
// Page views + unique visitors per day.
chQuery<{ day: string, pv: string | number, visitors: string | number }>(`
SELECT toDate(event_at) AS day,
countIf(event_type = '$page-view') AS pv,
uniqExactIf(assumeNotNull(user_id), event_type = '$page-view') AS visitors
FROM analytics_internal.events
WHERE event_type IN ('$page-view', '$click')
AND event_at >= {since:DateTime} AND event_at < {until:DateTime}
GROUP BY day ORDER BY day ASC
`, windowParams),
// Signups per day (users table).
chQuery<{ day: string, c: string | number }>(`
SELECT toDate(signed_up_at, 'UTC') AS day, count() AS c
FROM analytics_internal.users FINAL
WHERE ${userScope} AND is_anonymous = 0
AND signed_up_at >= {since:DateTime} AND signed_up_at < {until:DateTime}
GROUP BY day ORDER BY day ASC
`, windowParams),
// MAU + active projects, current vs prior 30d window (single pass over 60d).
chQuery<{ mauCur: string | number, mauPrev: string | number, projCur: string | number, projPrev: string | number }>(`
SELECT
uniqExactIf(assumeNotNull(user_id), event_at >= {mid:DateTime}) AS mauCur,
uniqExactIf(assumeNotNull(user_id), event_at < {mid:DateTime}) AS mauPrev,
uniqExactIf(project_id, event_at >= {mid:DateTime}) AS projCur,
uniqExactIf(project_id, event_at < {mid:DateTime}) AS projPrev
FROM analytics_internal.events
WHERE event_type = '$token-refresh' AND user_id IS NOT NULL
AND event_at >= {priorSince:DateTime} AND event_at < {until:DateTime}
`, twoWindowParams),
// User stock counts: total, verified, anonymous (now + as-of window start).
chQuery<{ total: string | number, totalPrev: string | number, verified: string | number, verifiedPrev: string | number, anonymous: string | number }>(`
SELECT
countIf(is_anonymous = 0) AS total,
countIf(is_anonymous = 0 AND signed_up_at < {mid:DateTime}) AS totalPrev,
countIf(is_anonymous = 0 AND ${verifiedSubquery}) AS verified,
countIf(is_anonymous = 0 AND signed_up_at < {mid:DateTime} AND ${verifiedSubquery}) AS verifiedPrev,
countIf(is_anonymous = 1) AS anonymous
FROM analytics_internal.users FINAL
WHERE ${userScope}
`, { branchId, mid: midParam }),
// Users by country (for the globe) over the window.
chQuery<{ country_code: string, c: string | number }>(`
SELECT country_code, count() AS c FROM (
SELECT user_id, argMax(cc, event_at) AS country_code FROM (
SELECT user_id, event_at, CAST(data.ip_info.country_code, 'Nullable(String)') AS cc
FROM analytics_internal.events
WHERE event_type = '$token-refresh' AND user_id IS NOT NULL
AND event_at >= {since:DateTime} AND event_at < {until:DateTime}
) WHERE cc IS NOT NULL GROUP BY user_id
) WHERE country_code IS NOT NULL GROUP BY country_code ORDER BY c DESC
`, windowParams),
// Dead-click health over the window.
chQuery<{ clicks: string | number, dead: string | number }>(`
SELECT count() AS clicks, sum(is_dead) AS dead
FROM analytics_internal.clickmap_events
WHERE event_at >= {since:DateTime} AND event_at < {until:DateTime}
`, windowParams),
// New / retained / reactivated split across all projects.
chQuery<{ day: string, total_count: string, new_count: string, retained_count: string, reactivated_count: string }>(`
SELECT
toString(w.day) AS day,
count() AS total_count,
countIf(f.first_date = w.day) AS new_count,
countIf(f.first_date < w.day AND w.prev_day = addDays(w.day, -1)) AS retained_count,
countIf(f.first_date < w.day AND (isNull(w.prev_day) OR w.prev_day < addDays(w.day, -1))) AS reactivated_count
FROM (
SELECT day, entity_id, lagInFrame(day, 1) OVER (PARTITION BY entity_id ORDER BY day) AS prev_day
FROM (
SELECT DISTINCT toDate(event_at) AS day, assumeNotNull(user_id) AS entity_id
FROM analytics_internal.events
WHERE event_type = '$token-refresh' AND user_id IS NOT NULL
AND event_at >= {since:DateTime} AND event_at < {until:DateTime}
AND coalesce(CAST(data.is_anonymous, 'Nullable(UInt8)'), 0) = 0
)
) AS w
LEFT JOIN (
SELECT assumeNotNull(user_id) AS entity_id, toDate(min(event_at)) AS first_date
FROM analytics_internal.events
WHERE event_type = '$token-refresh' AND user_id IS NOT NULL
AND event_at < {until:DateTime}
AND coalesce(CAST(data.is_anonymous, 'Nullable(UInt8)'), 0) = 0
GROUP BY entity_id
) AS f USING (entity_id)
GROUP BY w.day ORDER BY w.day ASC
`, windowParams),
// Per-project total users.
chQuery<CountRow>(`
SELECT project_id AS projectId, count() AS c
FROM analytics_internal.users FINAL
WHERE ${userScope} AND is_anonymous = 0 GROUP BY project_id
`, baseParams),
// Per-project verified users.
chQuery<CountRow>(`
SELECT project_id AS projectId, count() AS c
FROM analytics_internal.users FINAL
WHERE ${userScope} AND is_anonymous = 0 AND ${verifiedSubquery} GROUP BY project_id
`, baseParams),
// Per-project signups, current vs prior window.
chQuery<{ projectId: string, cur: string | number, prev: string | number }>(`
SELECT project_id AS projectId,
countIf(signed_up_at >= {mid:DateTime}) AS cur,
countIf(signed_up_at < {mid:DateTime}) AS prev
FROM analytics_internal.users FINAL
WHERE ${userScope} AND is_anonymous = 0
AND signed_up_at >= {priorSince:DateTime} AND signed_up_at < {until:DateTime}
GROUP BY project_id
`, twoWindowParams),
// Per-project active users, current vs prior window.
chQuery<{ projectId: string, cur: string | number, prev: string | number }>(`
SELECT project_id AS projectId,
uniqExactIf(assumeNotNull(user_id), event_at >= {mid:DateTime}) AS cur,
uniqExactIf(assumeNotNull(user_id), event_at < {mid:DateTime}) AS prev
FROM analytics_internal.events
WHERE event_type = '$token-refresh' AND user_id IS NOT NULL
AND event_at >= {priorSince:DateTime} AND event_at < {until:DateTime}
GROUP BY project_id
`, twoWindowParams),
// Per-project daily active sparkline (visible window).
chQuery<{ projectId: string, day: string, c: string | number }>(`
SELECT project_id AS projectId, toDate(event_at) AS day, uniqExact(assumeNotNull(user_id)) AS c
FROM analytics_internal.events
WHERE event_type = '$token-refresh' AND user_id IS NOT NULL
AND event_at >= {since:DateTime} AND event_at < {until:DateTime}
GROUP BY project_id, day
`, windowParams),
// Feature adoption signals (per project) from synced CH tables.
chQuery<CountRow>(`SELECT project_id AS projectId, count() AS c FROM analytics_internal.teams FINAL WHERE ${userScope} GROUP BY project_id`, baseParams),
chQuery<CountRow>(`SELECT project_id AS projectId, count() AS c FROM analytics_internal.connected_accounts FINAL WHERE ${userScope} GROUP BY project_id`, baseParams),
chQuery<CountRow>(`SELECT project_id AS projectId, count() AS c FROM analytics_internal.email_outboxes FINAL WHERE ${userScope} GROUP BY project_id`, baseParams),
chQuery<CountRow>(`SELECT project_id AS projectId, count() AS c FROM analytics_internal.events WHERE event_type = '$page-view' GROUP BY project_id`, {}),
]);
ch = {
dauSeries, pvSeries, signupSeries, mauProjects, userCounts, country, deadClicks, split,
totalsByProject, verifiedByProject, signupsByProject, activeByProject, sparkByProject,
teamsByProject, oauthByProject, emailsByProject, analyticsByProject,
};
} catch (cause) {
throw new HexclaveAssertionError(`Failed to load platform analytics from ClickHouse: ${cause instanceof Error ? cause.message : String(cause)}`, {
cause, userId: req.auth.user.id,
});
}
// Postgres-only signals: revenue (per project + daily), MRR (true recurring),
// auth-method split, email deliverability, payments/replay adoption.
let pg: {
revenueDaily: Array<{ day: string, cents: string | number }>,
revenueByProject: Array<{ projectId: string, cur: string | number, prev: string | number }>,
subscriptions: Array<{ projectId: string, product: unknown, priceId: string | null, quantity: number }>,
authMethods: Array<{ method: string, count: number }>,
email: Array<{ sent: number, delivered: number, bounced: number, error: number, in_progress: number, deliveredCur: number, finishedCur: number, deliveredPrev: number, finishedPrev: number }>,
paymentsRows: Array<{ projectId: string }>,
replayRows: Array<{ projectId: string }>,
};
try {
const replica = globalPrismaClient.$replica();
const since = windowStart;
const prior = priorStart;
const mid = windowStart;
const [revenueDaily, revenueByProject, subscriptions, authMethods, email, paymentsRows, replayRows] = await Promise.all([
replica.$queryRaw<Array<{ day: string, cents: string | number }>>(Prisma.sql`
SELECT TO_CHAR(si."createdAt"::date, 'YYYY-MM-DD') AS day, COALESCE(SUM(si."amountTotal"), 0)::bigint AS cents
FROM "SubscriptionInvoice" si JOIN "Tenancy" t ON t."id" = si."tenancyId"
WHERE si."amountTotal" IS NOT NULL AND si."status" = ANY(${REVENUE_INVOICE_STATUSES})
AND si."createdAt" >= ${since} AND t."projectId" <> ${INTERNAL_PROJECT_ID}
GROUP BY day ORDER BY day
`),
replica.$queryRaw<Array<{ projectId: string, cur: string | number, prev: string | number }>>(Prisma.sql`
SELECT t."projectId" AS "projectId",
COALESCE(SUM("amountTotal") FILTER (WHERE si."createdAt" >= ${mid}), 0)::bigint AS cur,
COALESCE(SUM("amountTotal") FILTER (WHERE si."createdAt" < ${mid}), 0)::bigint AS prev
FROM "SubscriptionInvoice" si JOIN "Tenancy" t ON t."id" = si."tenancyId"
WHERE si."amountTotal" IS NOT NULL AND si."status" = ANY(${REVENUE_INVOICE_STATUSES})
AND si."createdAt" >= ${prior} AND t."projectId" <> ${INTERNAL_PROJECT_ID}
GROUP BY t."projectId"
`),
replica.$queryRaw<Array<{ projectId: string, product: unknown, priceId: string | null, quantity: number }>>(Prisma.sql`
SELECT t."projectId" AS "projectId", s."product" AS product, s."priceId" AS "priceId", s."quantity" AS quantity
FROM "Subscription" s JOIN "Tenancy" t ON t."id" = s."tenancyId"
WHERE s."status"::text = ANY(${MRR_SUBSCRIPTION_STATUSES}) AND t."projectId" <> ${INTERNAL_PROJECT_ID}
`),
replica.$queryRaw<Array<{ method: string, count: number }>>(Prisma.sql`
SELECT method, COUNT(*)::int AS count FROM (
SELECT COALESCE(
oaam."configOAuthProviderId"::text,
CASE WHEN pam."authMethodId" IS NOT NULL THEN 'password' END,
CASE WHEN pkm."authMethodId" IS NOT NULL THEN 'passkey' END,
CASE WHEN oam."authMethodId" IS NOT NULL THEN 'otp' END,
'other'
) AS method
FROM "AuthMethod" am
JOIN "Tenancy" t ON t."id" = am."tenancyId"
LEFT JOIN "OAuthAuthMethod" oaam ON oaam."tenancyId" = am."tenancyId" AND oaam."authMethodId" = am."id"
LEFT JOIN "PasswordAuthMethod" pam ON pam."tenancyId" = am."tenancyId" AND pam."authMethodId" = am."id"
LEFT JOIN "PasskeyAuthMethod" pkm ON pkm."tenancyId" = am."tenancyId" AND pkm."authMethodId" = am."id"
LEFT JOIN "OtpAuthMethod" oam ON oam."tenancyId" = am."tenancyId" AND oam."authMethodId" = am."id"
WHERE t."projectId" <> ${INTERNAL_PROJECT_ID}
) sub GROUP BY method ORDER BY count DESC
`),
replica.$queryRaw<Array<{ sent: number, delivered: number, bounced: number, error: number, in_progress: number, deliveredCur: number, finishedCur: number, deliveredPrev: number, finishedPrev: number }>>(Prisma.sql`
SELECT
COUNT(*) FILTER (WHERE eo."finishedSendingAt" IS NOT NULL)::int AS sent,
COUNT(*) FILTER (WHERE eo."deliveredAt" IS NOT NULL)::int AS delivered,
COUNT(*) FILTER (WHERE eo."bouncedAt" IS NOT NULL)::int AS bounced,
COUNT(*) FILTER (WHERE eo."simpleStatus"::text = 'ERROR')::int AS error,
COUNT(*) FILTER (WHERE eo."simpleStatus"::text = 'IN_PROGRESS')::int AS in_progress,
COUNT(*) FILTER (WHERE eo."deliveredAt" IS NOT NULL AND eo."createdAt" >= ${mid})::int AS "deliveredCur",
COUNT(*) FILTER (WHERE eo."finishedSendingAt" IS NOT NULL AND eo."createdAt" >= ${mid})::int AS "finishedCur",
COUNT(*) FILTER (WHERE eo."deliveredAt" IS NOT NULL AND eo."createdAt" >= ${prior} AND eo."createdAt" < ${mid})::int AS "deliveredPrev",
COUNT(*) FILTER (WHERE eo."finishedSendingAt" IS NOT NULL AND eo."createdAt" >= ${prior} AND eo."createdAt" < ${mid})::int AS "finishedPrev"
FROM "EmailOutbox" eo JOIN "Tenancy" t ON t."id" = eo."tenancyId"
WHERE t."projectId" <> ${INTERNAL_PROJECT_ID}
`),
replica.$queryRaw<Array<{ projectId: string }>>(Prisma.sql`
SELECT DISTINCT t."projectId" AS "projectId"
FROM "Subscription" s JOIN "Tenancy" t ON t."id" = s."tenancyId"
WHERE s."status" IN ('active', 'trialing', 'paused') AND t."projectId" <> ${INTERNAL_PROJECT_ID}
`),
replica.$queryRaw<Array<{ projectId: string }>>(Prisma.sql`
SELECT DISTINCT t."projectId" AS "projectId"
FROM "SessionReplay" sr JOIN "Tenancy" t ON t."id" = sr."tenancyId"
WHERE t."projectId" <> ${INTERNAL_PROJECT_ID}
`),
]);
pg = { revenueDaily, revenueByProject, subscriptions, authMethods, email, paymentsRows, replayRows };
} catch (cause) {
throw new HexclaveAssertionError(`Failed to load platform analytics from Postgres: ${cause instanceof Error ? cause.message : String(cause)}`, {
cause, userId: req.auth.user.id,
});
}
// ---- Assemble series ----
const dauByDay = new Map(ch.dauSeries.map((r) => [r.day.split("T")[0], num(r.c)]));
const pvByDay = new Map(ch.pvSeries.map((r) => [r.day.split("T")[0], { pv: num(r.pv), visitors: num(r.visitors) }]));
const signupByDay = new Map(ch.signupSeries.map((r) => [r.day.split("T")[0], num(r.c)]));
const revenueByDay = new Map(pg.revenueDaily.map((r) => [r.day, num(r.cents)]));
const series = windowDays.map((date) => ({
date,
signups: signupByDay.get(date) ?? 0,
active_users: dauByDay.get(date) ?? 0,
page_views: pvByDay.get(date)?.pv ?? 0,
visitors: pvByDay.get(date)?.visitors ?? 0,
revenue_cents: revenueByDay.get(date) ?? 0,
}));
// ---- Activity split ----
const splitByDay = new Map(ch.split.map((r) => [r.day.split("T")[0], r]));
const splitField = (field: "total_count" | "new_count" | "retained_count" | "reactivated_count") =>
windowDays.map((date) => ({ date, activity: num(splitByDay.get(date)?.[field]) }));
const activity_split = {
total: splitField("total_count"),
new: splitField("new_count"),
retained: splitField("retained_count"),
reactivated: splitField("reactivated_count"),
};
// ---- KPIs ----
const mp = ch.mauProjects[0] ?? { mauCur: 0, mauPrev: 0, projCur: 0, projPrev: 0 };
const uc = ch.userCounts[0] ?? { total: 0, totalPrev: 0, verified: 0, verifiedPrev: 0, anonymous: 0 };
const half = Math.max(1, WINDOW_DAYS);
const dauAvgCur = Math.round(series.reduce((s, p) => s + p.active_users, 0) / half);
const mauCur = num(mp.mauCur);
const mauPrev = num(mp.mauPrev);
const stick = (dau: number, mau: number) => mau > 0 ? Number(((dau / mau) * 100).toFixed(1)) : 0;
const signupsCur = series.reduce((s, p) => s + p.signups, 0);
const emailRow = pg.email[0] ?? { sent: 0, delivered: 0, bounced: 0, error: 0, in_progress: 0, deliveredCur: 0, finishedCur: 0, deliveredPrev: 0, finishedPrev: 0 };
const rate = (n: number, d: number) => d > 0 ? Number(((n / d) * 100).toFixed(1)) : 0;
// MRR (true recurring, normalized to monthly cents).
let mrrCents = 0;
for (const s of pg.subscriptions) {
mrrCents += monthlyRecurringCents(s.product, s.priceId, num(s.quantity) || 1);
}
const kpis = {
active_projects: { value: num(mp.projCur), prev: num(mp.projPrev) },
total_users: { value: num(uc.total), prev: num(uc.totalPrev) },
verified_users: { value: num(uc.verified), prev: num(uc.verifiedPrev) },
mau: { value: mauCur, prev: mauPrev },
dau_avg: { value: dauAvgCur, prev: null },
stickiness: { value: stick(dauAvgCur, mauCur), prev: null },
new_signups: { value: signupsCur, prev: null },
mrr_cents: { value: mrrCents, prev: null },
active_subscriptions: { value: pg.subscriptions.length, prev: null },
email_deliverability_rate: {
value: rate(emailRow.deliveredCur, emailRow.finishedCur),
prev: emailRow.finishedPrev > 0 ? rate(emailRow.deliveredPrev, emailRow.finishedPrev) : null,
},
};
// ---- Breakdowns ----
const usersByCountry: Record<string, number> = {};
for (const r of ch.country) {
if (r.country_code) usersByCountry[r.country_code.toUpperCase()] = num(r.c);
}
const nonAnon = num(uc.total);
const verified = num(uc.verified);
const breakdowns = {
auth_methods: pg.authMethods.map((m) => ({ method: m.method, count: num(m.count) })).filter((m) => m.count > 0),
users_by_status: {
verified,
unverified: Math.max(0, nonAnon - verified),
anonymous: num(uc.anonymous),
},
users_by_country: usersByCountry,
email: {
sent: emailRow.sent,
delivered: emailRow.delivered,
bounced: emailRow.bounced,
error: emailRow.error,
in_progress: emailRow.in_progress,
},
dead_click_rate: rate(num(ch.deadClicks[0]?.dead), num(ch.deadClicks[0]?.clicks)),
};
// ---- Feature adoption ----
const keysWithCount = (rows: CountRow[]) => rowsToMap(rows);
const countProjects = (map: Map<string, number>) => {
let n = 0;
for (const [id, c] of map) if (c > 0 && projectInfo.has(id) && id !== INTERNAL_PROJECT_ID) n += 1;
return n;
};
const countList = (ids: Iterable<string>) => {
const seen = new Set<string>();
for (const id of ids) if (projectInfo.has(id) && id !== INTERNAL_PROJECT_ID) seen.add(id);
return seen.size;
};
const teamsMap = keysWithCount(ch.teamsByProject);
const oauthMap = keysWithCount(ch.oauthByProject);
const emailsMap = keysWithCount(ch.emailsByProject);
const analyticsMap = keysWithCount(ch.analyticsByProject);
const feature_adoption = [
{ feature: "teams", projects_using: countProjects(teamsMap) },
{ feature: "oauth", projects_using: countProjects(oauthMap) },
{ feature: "emails", projects_using: countProjects(emailsMap) },
{ feature: "analytics", projects_using: countProjects(analyticsMap) },
{ feature: "payments", projects_using: countList(pg.paymentsRows.map((r) => r.projectId)) },
{ feature: "session_replay", projects_using: countList(pg.replayRows.map((r) => r.projectId)) },
];
// ---- Per-project leaderboard ----
const totalsMap = rowsToMap(ch.totalsByProject);
const verifiedMap = rowsToMap(ch.verifiedByProject);
const signupsMap = new Map(ch.signupsByProject.map((r) => [r.projectId, { cur: num(r.cur), prev: num(r.prev) }]));
const activeMap = new Map(ch.activeByProject.map((r) => [r.projectId, { cur: num(r.cur), prev: num(r.prev) }]));
const revenueMap = new Map(pg.revenueByProject.map((r) => [r.projectId, { cur: num(r.cur), prev: num(r.prev) }]));
const sparkIndex = new Map<string, Map<string, number>>();
for (const r of ch.sparkByProject) {
const day = r.day.split("T")[0];
let m = sparkIndex.get(r.projectId);
if (!m) {
m = new Map();
sparkIndex.set(r.projectId, m);
}
m.set(day, num(r.c));
}
const featureSet = (id: string): string[] => {
const f: string[] = [];
if ((teamsMap.get(id) ?? 0) > 0) f.push("teams");
if ((oauthMap.get(id) ?? 0) > 0) f.push("oauth");
if ((emailsMap.get(id) ?? 0) > 0) f.push("emails");
if ((analyticsMap.get(id) ?? 0) > 0) f.push("analytics");
return f;
};
const paymentsSet = new Set(pg.paymentsRows.map((r) => r.projectId));
const replaySet = new Set(pg.replayRows.map((r) => r.projectId));
const projects = projectRows.map((p) => {
const sp = sparkIndex.get(p.id);
const features = featureSet(p.id);
if (paymentsSet.has(p.id)) features.push("payments");
if (replaySet.has(p.id)) features.push("session_replay");
return {
id: p.id,
display_name: p.displayName,
created_at: p.createdAt.toISOString(),
total_users: totalsMap.get(p.id) ?? 0,
verified_users: verifiedMap.get(p.id) ?? 0,
active_users: activeMap.get(p.id)?.cur ?? 0,
active_users_prev: activeMap.get(p.id)?.prev ?? 0,
signups: signupsMap.get(p.id)?.cur ?? 0,
signups_prev: signupsMap.get(p.id)?.prev ?? 0,
revenue_cents: revenueMap.get(p.id)?.cur ?? 0,
revenue_cents_prev: revenueMap.get(p.id)?.prev ?? 0,
features,
sparkline: windowDays.map((d) => sp?.get(d) ?? 0),
};
});
projects.sort((a, b) => b.total_users - a.total_users || b.active_users - a.active_users);
return {
statusCode: 200 as const,
bodyType: "json" as const,
body: {
generated_at: now.toISOString(),
window_days: WINDOW_DAYS,
kpis,
series,
activity_split,
breakdowns,
feature_adoption,
projects: projects.slice(0, LEADERBOARD_LIMIT),
},
};
},
});
function emptyBody(now: Date) {
const zeroKpi = { value: 0, prev: null };
return {
generated_at: now.toISOString(),
window_days: WINDOW_DAYS,
kpis: {
active_projects: zeroKpi,
total_users: zeroKpi,
verified_users: zeroKpi,
mau: zeroKpi,
dau_avg: zeroKpi,
stickiness: zeroKpi,
new_signups: zeroKpi,
mrr_cents: zeroKpi,
active_subscriptions: zeroKpi,
email_deliverability_rate: zeroKpi,
},
series: [],
activity_split: { total: [], new: [], retained: [], reactivated: [] },
breakdowns: {
auth_methods: [],
users_by_status: { verified: 0, unverified: 0, anonymous: 0 },
users_by_country: {},
email: { sent: 0, delivered: 0, bounced: 0, error: 0, in_progress: 0 },
dead_click_rate: 0,
},
feature_adoption: [],
projects: [],
};
}

View File

@ -0,0 +1,25 @@
import { UsersCrud } from "@hexclave/shared/dist/interface/crud/users";
import { StatusError } from "@hexclave/shared/dist/utils/errors";
import { listManagedProjectIds } from "./projects";
// Authorization for platform-wide (cross-customer) internal endpoints.
//
// Being a signed-in user of the "internal" project is NOT sufficient: the
// internal project's publishable client key is public and, on deployments with
// open dashboard sign-up, anyone can create an internal-project account (in their
// own team). Access is therefore gated on membership of the team that OWNS the
// internal project — i.e. the platform team. That is exactly what
// `listManagedProjectIds` encodes (a user manages a project when they belong to
// its owner team), so the internal project appears in that list only for platform
// team members.
export async function isPlatformAdmin(user: UsersCrud["Admin"]["Read"]): Promise<boolean> {
const managedProjectIds = await listManagedProjectIds(user);
return managedProjectIds.includes("internal");
}
export async function ensurePlatformAdmin(user: UsersCrud["Admin"]["Read"]): Promise<void> {
if (!(await isPlatformAdmin(user))) {
throw new StatusError(403, "You do not have access to platform analytics.");
}
}

View File

@ -92,7 +92,7 @@
"libsodium-wrappers": "^0.8.2",
"lodash": "^4.17.21",
"motion": "^12.39.0",
"next": "16.2.7",
"next": "16.2.9",
"next-themes": "^0.2.1",
"posthog-js": "^1.336.1",
"qrcode": "^1.5.4",

View File

@ -0,0 +1,655 @@
'use client';
import { Skeleton, Typography } from "@/components/ui";
import { Card, CardContent } from "@/components/ui/card";
import { DesignAnalyticsCard } from "@/components/design-components";
import { hexclaveAppInternalsSymbol } from "@/lib/hexclave-app-internals";
import { cn } from "@/lib/utils";
import {
ChartLineUpIcon,
CreditCardIcon,
CursorClickIcon,
EnvelopeSimpleIcon,
FingerprintSimpleIcon,
LockKeyIcon,
MonitorPlayIcon,
UsersThreeIcon,
} from "@phosphor-icons/react";
import { useStackApp, useUser } from "@hexclave/next";
import { captureError } from "@hexclave/shared/dist/utils/errors";
import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
import { useEffect, useMemo, useState } from "react";
import { PageLayout } from "../page-layout";
import { useProjectId } from "../use-admin-app";
import {
ComposedAnalyticsChart,
DonutChartDisplay,
StackedBarChartDisplay,
type ComposedDataPoint,
type StackedDataPoint,
} from "../(overview)/line-chart";
import { GlobeSection } from "../(overview)/globe";
type Kpi = { value: number, prev: number | null };
type SeriesPoint = {
date: string,
signups: number,
active_users: number,
page_views: number,
visitors: number,
revenue_cents: number,
};
type SplitPoint = { date: string, activity: number };
type ProjectRow = {
id: string,
display_name: string,
created_at: string,
total_users: number,
verified_users: number,
active_users: number,
active_users_prev: number,
signups: number,
signups_prev: number,
revenue_cents: number,
revenue_cents_prev: number,
features: string[],
sparkline: number[],
};
type PlatformAnalytics = {
generated_at: string,
window_days: number,
kpis: {
active_projects: Kpi,
total_users: Kpi,
verified_users: Kpi,
mau: Kpi,
dau_avg: Kpi,
stickiness: Kpi,
new_signups: Kpi,
mrr_cents: Kpi,
active_subscriptions: Kpi,
email_deliverability_rate: Kpi,
},
series: SeriesPoint[],
activity_split: { total: SplitPoint[], new: SplitPoint[], retained: SplitPoint[], reactivated: SplitPoint[] },
breakdowns: {
auth_methods: Array<{ method: string, count: number }>,
users_by_status: { verified: number, unverified: number, anonymous: number },
users_by_country: Record<string, number>,
email: { sent: number, delivered: number, bounced: number, error: number, in_progress: number },
dead_click_rate: number,
},
feature_adoption: Array<{ feature: string, projects_using: number }>,
projects: ProjectRow[],
};
type LoadState =
| { status: "loading" }
| { status: "forbidden" }
| { status: "error" }
| { status: "ok", data: PlatformAnalytics };
type HexclaveAppInternals = {
sendRequest: (path: string, requestOptions: RequestInit, requestType?: "client" | "server" | "admin") => Promise<Response>,
};
function getStackAppInternals(appValue: unknown): HexclaveAppInternals {
if (appValue == null || typeof appValue !== "object") {
throw new Error("The Stack app instance is unavailable.");
}
const internals = Reflect.get(appValue, hexclaveAppInternalsSymbol);
if (
internals == null ||
typeof internals !== "object" ||
!("sendRequest" in internals) ||
typeof (internals as HexclaveAppInternals).sendRequest !== "function"
) {
throw new Error("The Stack client app cannot send internal requests.");
}
return internals as HexclaveAppInternals;
}
function formatNumber(n: number): string {
return Math.round(n).toLocaleString();
}
function formatCompact(n: number): string {
if (Math.abs(n) >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
if (Math.abs(n) >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
return formatNumber(n);
}
function formatUsdFromCents(cents: number): string {
const dollars = cents / 100;
if (Math.abs(dollars) >= 1_000) return `$${(dollars / 1_000).toFixed(1)}k`;
return `$${dollars.toLocaleString(undefined, { maximumFractionDigits: 0 })}`;
}
function growthPct(value: number, prev: number): number | null {
if (prev === 0) return value === 0 ? 0 : null;
return Number((((value - prev) / prev) * 100).toFixed(1));
}
export default function PageClient() {
const projectId = useProjectId();
useUser({ or: "redirect", projectIdMustMatch: "internal" });
if (projectId !== "internal") {
return null;
}
return (
<PageLayout
title="Platform Analytics"
description="Platform-wide usage across every project. Visible only to the internal team."
>
<PlatformAnalyticsContent />
</PageLayout>
);
}
function PlatformAnalyticsContent() {
const app = useStackApp();
const appInternals = useMemo(() => getStackAppInternals(app), [app]);
const [state, setState] = useState<LoadState>({ status: "loading" });
const [range, setRange] = useState<7 | 30>(30);
useEffect(() => {
let cancelled = false;
runAsynchronously(async () => {
setState({ status: "loading" });
try {
const response = await appInternals.sendRequest("/internal/platform-analytics", {}, "client");
if (response.status === 403) {
if (!cancelled) setState({ status: "forbidden" });
return;
}
if (!response.ok) {
throw new Error(`Failed to load platform analytics: ${response.status} ${await response.text()}`);
}
const body = await response.json() as unknown;
if (body == null || typeof body !== "object" || !Array.isArray((body as PlatformAnalytics).projects)) {
throw new Error("Platform analytics endpoint returned an invalid response.");
}
if (!cancelled) setState({ status: "ok", data: body as PlatformAnalytics });
} catch (e) {
if (cancelled) return;
setState({ status: "error" });
captureError("platform-analytics-load", e);
}
});
return () => {
cancelled = true;
};
}, [appInternals]);
if (state.status === "loading") {
return (
<div className="flex flex-col gap-6">
<div className="grid grid-cols-2 gap-3 lg:grid-cols-5">
{Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-24 w-full rounded-xl" />)}
</div>
<Skeleton className="h-80 w-full rounded-xl" />
<Skeleton className="h-96 w-full rounded-xl" />
</div>
);
}
if (state.status === "forbidden") {
return (
<Card>
<CardContent className="flex flex-col items-center gap-2 py-12 text-center">
<LockKeyIcon className="h-6 w-6 text-muted-foreground" />
<Typography type="h3">Access restricted</Typography>
<Typography variant="secondary" className="max-w-md text-sm">
Platform analytics is limited to members of the internal project&apos;s team.
</Typography>
</CardContent>
</Card>
);
}
if (state.status === "error") {
return (
<Card>
<CardContent className="py-10 text-center">
<Typography variant="secondary">Could not load platform analytics. Please try again.</Typography>
</CardContent>
</Card>
);
}
return <Dashboard data={state.data} range={range} onRangeChange={setRange} />;
}
function Dashboard({
data,
range,
onRangeChange,
}: {
data: PlatformAnalytics,
range: 7 | 30,
onRangeChange: (range: 7 | 30) => void,
}) {
const slice = <T,>(arr: T[]): T[] => (range === 30 ? arr : arr.slice(-range));
const series = slice(data.series);
const composed: ComposedDataPoint[] = series.map((p) => ({
date: p.date,
new_cents: p.revenue_cents,
refund_cents: 0,
page_views: p.page_views,
visitors: p.visitors,
dau: p.active_users,
}));
const splitByDate = new Map<string, StackedDataPoint>();
for (const p of data.activity_split.total) {
splitByDate.set(p.date, { date: p.date, new: 0, retained: 0, reactivated: 0 });
}
for (const p of data.activity_split.new) {
const d = splitByDate.get(p.date);
if (d) d.new = p.activity;
}
for (const p of data.activity_split.retained) {
const d = splitByDate.get(p.date);
if (d) d.retained = p.activity;
}
for (const p of data.activity_split.reactivated) {
const d = splitByDate.get(p.date);
if (d) d.reactivated = p.activity;
}
const stacked = slice([...splitByDate.values()]);
const k = data.kpis;
const tiles = [
{ label: "Active projects", kpi: k.active_projects, format: formatNumber },
{ label: "Total users", kpi: k.total_users, format: formatCompact },
{ label: "Verified users", kpi: k.verified_users, format: formatCompact },
{ label: "MAU", kpi: k.mau, format: formatCompact },
{ label: "Stickiness", kpi: k.stickiness, format: (n: number) => `${n}%`, suffixDelta: "pp" },
{ label: `New sign-ups (${data.window_days}d)`, kpi: k.new_signups, format: formatCompact },
{ label: "MRR", kpi: k.mrr_cents, format: formatUsdFromCents },
{ label: "Active subscriptions", kpi: k.active_subscriptions, format: formatNumber },
{ label: "Email deliverability", kpi: k.email_deliverability_rate, format: (n: number) => `${n}%`, suffixDelta: "pp" },
{ label: `Avg DAU (${data.window_days}d)`, kpi: k.dau_avg, format: formatCompact },
];
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-end">
<RangeToggle range={range} onRangeChange={onRangeChange} />
</div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
{tiles.map((tile) => (
<KpiTile key={tile.label} label={tile.label} kpi={tile.kpi} format={tile.format} suffixDelta={tile.suffixDelta} />
))}
</div>
<div className="grid grid-cols-1 gap-6 xl:grid-cols-3">
<ChartCard title="Platform growth" subtitle="Active users, visitors, page views and revenue" className="xl:col-span-2" gradient="blue">
{composed.length === 0
? <EmptyChart />
: <ComposedAnalyticsChart datapoints={composed} showVisitors showPageViews showRevenue height={300} />}
</ChartCard>
<ChartCard title="Growth quality" subtitle="New / retained / reactivated users" gradient="green">
{stacked.length === 0
? <EmptyChart />
: <StackedBarChartDisplay datapoints={stacked} height={300} />}
</ChartCard>
</div>
<ProjectLeaderboard projects={data.projects} windowDays={data.window_days} />
<div className="grid grid-cols-1 gap-6 xl:grid-cols-3">
<ChartCard title="Where users are" subtitle="Active users by country" gradient="cyan">
<div className="h-[320px] w-full">
<GlobeSection countryData={data.breakdowns.users_by_country} totalUsers={k.total_users.value} interactive />
</div>
</ChartCard>
<ChartCard title="Sign-in methods" subtitle="How end users authenticate" gradient="purple">
{data.breakdowns.auth_methods.length === 0
? <EmptyChart />
: <DonutChartDisplay datapoints={data.breakdowns.auth_methods} gradientColor="purple" />}
</ChartCard>
<ChartCard title="User mix" subtitle="Verified, unverified and anonymous" gradient="orange">
<DonutChartDisplay
datapoints={[
{ method: "Verified", count: data.breakdowns.users_by_status.verified },
{ method: "Unverified", count: data.breakdowns.users_by_status.unverified },
{ method: "Anonymous", count: data.breakdowns.users_by_status.anonymous },
]}
gradientColor="orange"
/>
</ChartCard>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
<FeatureAdoption features={data.feature_adoption} totalProjects={k.active_projects.value || data.projects.length} />
<EmailHealth email={data.breakdowns.email} />
<UxHealth deadClickRate={data.breakdowns.dead_click_rate} />
</div>
</div>
);
}
function RangeToggle({ range, onRangeChange }: { range: 7 | 30, onRangeChange: (range: 7 | 30) => void }) {
return (
<div className="inline-flex items-center gap-1 rounded-xl bg-foreground/[0.06] p-1">
{([7, 30] as const).map((value) => (
<button
key={value}
type="button"
onClick={() => onRangeChange(value)}
className={cn(
"rounded-lg px-3 py-1 text-xs font-medium transition-colors hover:transition-none",
range === value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground",
)}
>
{value}D
</button>
))}
</div>
);
}
function KpiTile({
label,
kpi,
format,
suffixDelta,
}: {
label: string,
kpi: Kpi,
format: (n: number) => string,
suffixDelta?: string,
}) {
const delta = kpi.prev == null ? null : suffixDelta === "pp"
? Number((kpi.value - kpi.prev).toFixed(1))
: growthPct(kpi.value, kpi.prev);
return (
<Card>
<CardContent className="flex flex-col gap-1 py-4">
<Typography variant="secondary" className="truncate text-[11px] uppercase tracking-wide">{label}</Typography>
<div className="flex items-baseline gap-2">
<span className="text-2xl font-semibold tabular-nums text-foreground">{format(kpi.value)}</span>
{delta != null && delta !== 0 && (
<span className={cn(
"text-xs font-semibold tabular-nums",
delta > 0 ? "text-emerald-500 dark:text-emerald-400" : "text-red-500 dark:text-red-400",
)}>
{delta > 0 ? "+" : ""}{delta}{suffixDelta === "pp" ? "pp" : "%"}
</span>
)}
</div>
</CardContent>
</Card>
);
}
function ChartCard({
title,
subtitle,
className,
gradient,
children,
}: {
title: string,
subtitle?: string,
className?: string,
gradient: "blue" | "cyan" | "purple" | "green" | "orange" | "slate",
children: React.ReactNode,
}) {
return (
<DesignAnalyticsCard gradient={gradient} className={cn("flex flex-col", className)}>
<div className="flex flex-col gap-0.5 px-5 pt-4">
<Typography className="text-sm font-semibold text-foreground">{title}</Typography>
{subtitle && <Typography variant="secondary" className="text-xs">{subtitle}</Typography>}
</div>
<div className="min-h-0 flex-1 px-3 pb-3 pt-2">{children}</div>
</DesignAnalyticsCard>
);
}
function EmptyChart() {
return (
<div className="flex h-[280px] items-center justify-center">
<Typography variant="secondary" className="text-xs">No data for this period.</Typography>
</div>
);
}
const STATUS_STYLES: Record<string, string> = {
Growing: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
Declining: "bg-red-500/10 text-red-600 dark:text-red-400",
New: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
Dormant: "bg-foreground/[0.06] text-muted-foreground",
Flat: "bg-foreground/[0.06] text-muted-foreground",
};
function projectStatus(project: ProjectRow, windowDays: number): string {
const ageDays = (Date.now() - new Date(project.created_at).getTime()) / (24 * 60 * 60 * 1000);
if (ageDays <= windowDays) return "New";
if (project.active_users === 0) return "Dormant";
const g = growthPct(project.active_users, project.active_users_prev);
if (g == null) return "Growing";
if (g > 10) return "Growing";
if (g < -10) return "Declining";
return "Flat";
}
type SortKey = "total_users" | "verified" | "active_users" | "signups" | "signup_growth" | "revenue" | "revenue_growth";
function ProjectLeaderboard({ projects, windowDays }: { projects: ProjectRow[], windowDays: number }) {
const [sortKey, setSortKey] = useState<SortKey>("total_users");
const [search, setSearch] = useState("");
const sorted = useMemo(() => {
const value = (p: ProjectRow): number => {
switch (sortKey) {
case "verified": {
return p.verified_users;
}
case "active_users": {
return p.active_users;
}
case "signups": {
return p.signups;
}
case "signup_growth": {
return growthPct(p.signups, p.signups_prev) ?? -Infinity;
}
case "revenue": {
return p.revenue_cents;
}
case "revenue_growth": {
return growthPct(p.revenue_cents, p.revenue_cents_prev) ?? -Infinity;
}
default: {
return p.total_users;
}
}
};
const q = search.trim().toLowerCase();
return projects
.filter((p) => q === "" || p.display_name.toLowerCase().includes(q) || p.id.toLowerCase().includes(q))
.slice()
.sort((a, b) => value(b) - value(a));
}, [projects, sortKey, search]);
const header = (key: SortKey, label: string) => (
<button
type="button"
onClick={() => setSortKey(key)}
className={cn("text-right tabular-nums transition-colors hover:text-foreground", sortKey === key ? "text-foreground" : "")}
>
{label}{sortKey === key ? " ↓" : ""}
</button>
);
return (
<Card>
<CardContent className="flex flex-col gap-3 py-5">
<div className="flex items-center justify-between gap-3">
<Typography className="text-sm font-semibold">Projects</Typography>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search projects"
className="w-48 rounded-lg border border-border/60 bg-transparent px-2.5 py-1 text-xs outline-none focus:border-foreground/30"
/>
</div>
<div className="overflow-x-auto">
<div className="min-w-[820px]">
<div className="grid grid-cols-[1.5rem_minmax(10rem,1.5fr)_5rem_4.5rem_5rem_5rem_5rem_5rem_4rem] items-center gap-3 border-b border-border/60 pb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
<span>#</span>
<span>Project</span>
{header("total_users", "Users")}
{header("verified", "Verified")}
{header("active_users", "Active")}
{header("signups", "Sign-ups")}
{header("signup_growth", "Growth")}
{header("revenue", "Revenue")}
<span className="text-right">Trend</span>
</div>
<div className="divide-y divide-border/40">
{sorted.map((project, index) => {
const status = projectStatus(project, windowDays);
const sg = growthPct(project.signups, project.signups_prev);
return (
<div
key={project.id}
className="grid grid-cols-[1.5rem_minmax(10rem,1.5fr)_5rem_4.5rem_5rem_5rem_5rem_5rem_4rem] items-center gap-3 py-2.5 text-sm"
>
<span className="tabular-nums text-muted-foreground">{index + 1}</span>
<div className="flex min-w-0 items-center gap-2">
<span className="truncate font-medium text-foreground">{project.display_name || project.id}</span>
<span className={cn("shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-semibold", STATUS_STYLES[status])}>{status}</span>
</div>
<span className="text-right tabular-nums text-foreground">{formatCompact(project.total_users)}</span>
<span className="text-right tabular-nums text-muted-foreground">{formatCompact(project.verified_users)}</span>
<span className="text-right tabular-nums text-muted-foreground">{formatCompact(project.active_users)}</span>
<span className="text-right tabular-nums text-muted-foreground">{formatCompact(project.signups)}</span>
<span className={cn(
"text-right text-xs tabular-nums",
sg == null ? "text-muted-foreground" : sg > 0 ? "text-emerald-500 dark:text-emerald-400" : sg < 0 ? "text-red-500 dark:text-red-400" : "text-muted-foreground",
)}>
{sg == null ? "—" : `${sg > 0 ? "+" : ""}${sg}%`}
</span>
<span className="text-right tabular-nums text-muted-foreground">{project.revenue_cents > 0 ? formatUsdFromCents(project.revenue_cents) : "—"}</span>
<div className="flex justify-end"><Sparkline values={project.sparkline} /></div>
</div>
);
})}
{sorted.length === 0 && (
<Typography variant="secondary" className="py-6 text-center text-sm">No projects match.</Typography>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
}
function Sparkline({ values }: { values: number[] }) {
const width = 56;
const height = 18;
if (values.length === 0) return <span className="text-muted-foreground"></span>;
const max = Math.max(1, ...values);
const step = values.length > 1 ? width / (values.length - 1) : width;
const points = values.map((v, i) => `${(i * step).toFixed(1)},${(height - (v / max) * height).toFixed(1)}`).join(" ");
return (
<svg width={width} height={height} className="overflow-visible text-foreground/40" aria-hidden>
<polyline points={points} fill="none" stroke="currentColor" strokeWidth={1.25} strokeLinejoin="round" strokeLinecap="round" />
</svg>
);
}
const FEATURE_META = new Map<string, { label: string, icon: React.ElementType }>([
["teams", { label: "Teams", icon: UsersThreeIcon }],
["oauth", { label: "OAuth sign-in", icon: FingerprintSimpleIcon }],
["emails", { label: "Emails", icon: EnvelopeSimpleIcon }],
["analytics", { label: "Analytics SDK", icon: CursorClickIcon }],
["payments", { label: "Payments", icon: CreditCardIcon }],
["session_replay", { label: "Session replay", icon: MonitorPlayIcon }],
]);
function FeatureAdoption({ features, totalProjects }: { features: Array<{ feature: string, projects_using: number }>, totalProjects: number }) {
const denominator = Math.max(1, totalProjects);
return (
<Card>
<CardContent className="flex flex-col gap-3 py-5">
<Typography className="text-sm font-semibold">Feature adoption</Typography>
{features.map((feature) => {
const meta = FEATURE_META.get(feature.feature);
const Icon = meta?.icon ?? ChartLineUpIcon;
const pct = Math.round((feature.projects_using / denominator) * 100);
return (
<div key={feature.feature} className="flex flex-col gap-1.5">
<div className="flex items-center justify-between gap-2 text-sm">
<span className="flex items-center gap-2 text-foreground">
<Icon className="h-4 w-4 text-muted-foreground" weight="regular" />
{meta?.label ?? feature.feature}
</span>
<span className="tabular-nums text-muted-foreground">{formatNumber(feature.projects_using)} <span className="text-xs">({pct}%)</span></span>
</div>
<div className="h-1.5 w-full overflow-hidden rounded-full bg-foreground/[0.06]">
<div className="h-full rounded-full bg-foreground/30" style={{ width: `${pct}%` }} />
</div>
</div>
);
})}
</CardContent>
</Card>
);
}
function EmailHealth({ email }: { email: PlatformAnalytics["breakdowns"]["email"] }) {
const rows = [
{ label: "Sent", value: email.sent, color: "bg-blue-500" },
{ label: "Delivered", value: email.delivered, color: "bg-emerald-500" },
{ label: "Bounced", value: email.bounced, color: "bg-red-500" },
{ label: "Errored", value: email.error, color: "bg-amber-500" },
{ label: "In progress", value: email.in_progress, color: "bg-sky-400" },
];
return (
<Card>
<CardContent className="flex flex-col gap-3 py-5">
<Typography className="text-sm font-semibold">Email health</Typography>
{rows.map((row) => (
<div key={row.label} className="flex items-center justify-between gap-2 text-sm">
<span className="flex items-center gap-2 text-foreground">
<span className={cn("h-1.5 w-1.5 rounded-full", row.color)} />
{row.label}
</span>
<span className="tabular-nums text-muted-foreground">{formatNumber(row.value)}</span>
</div>
))}
</CardContent>
</Card>
);
}
function UxHealth({ deadClickRate }: { deadClickRate: number }) {
return (
<Card>
<CardContent className="flex flex-col gap-2 py-5">
<Typography className="text-sm font-semibold">UX health</Typography>
<Typography variant="secondary" className="text-xs">Dead clicks (clicks with no observable effect)</Typography>
<div className="flex items-baseline gap-2">
<span className={cn(
"text-3xl font-semibold tabular-nums",
deadClickRate > 10 ? "text-red-500 dark:text-red-400" : deadClickRate > 4 ? "text-amber-500 dark:text-amber-400" : "text-emerald-500 dark:text-emerald-400",
)}>
{deadClickRate}%
</span>
</div>
<div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-foreground/[0.06]">
<div className="h-full rounded-full bg-foreground/30" style={{ width: `${Math.min(100, deadClickRate)}%` }} />
</div>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,9 @@
import PageClient from "./page-client";
export const metadata = {
title: "Platform Analytics",
};
export default function Page() {
return <PageClient />;
}

View File

@ -28,6 +28,7 @@ import {
CaretDownIcon,
CaretRightIcon,
ChartBarIcon,
ChartPieSliceIcon,
CubeIcon,
GearIcon,
GlobeIcon,
@ -108,6 +109,16 @@ const dashboardsItem: Item = {
type: 'item',
};
// Internal-only: platform-wide analytics across every project. Rendered solely
// when the active project is the internal (platform team) dashboard.
const platformAnalyticsItem: Item = {
name: "Platform Analytics",
href: "/platform-analytics",
regex: /^\/projects\/[^\/]+\/platform-analytics(\/.*)?$/,
icon: ChartPieSliceIcon,
type: 'item',
};
const projectSettingsItem: AppSection = {
name: "Project Settings",
icon: GearIcon,
@ -528,6 +539,14 @@ function SidebarContent({
href={`/projects/${projectId}${dashboardsItem.href}`}
isCollapsed={isCollapsed}
/>
{projectId === "internal" && (
<NavItem
item={platformAnalyticsItem}
onClick={onNavigate}
href={`/projects/${projectId}${platformAnalyticsItem.href}`}
isCollapsed={isCollapsed}
/>
)}
</div>
<div className={cn("mt-6 mb-3 transition-opacity duration-200", isCollapsed ? "opacity-0 h-0 mt-2 mb-0 overflow-hidden" : "opacity-100")}>

View File

@ -21,7 +21,7 @@
"@hexclave/shared": "workspace:*",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"next": "16.2.7",
"next": "16.2.9",
"react-markdown": "^10.1.0",
"zod": "^3.24.0",
"remark-gfm": "^4.0.1",

View File

@ -15,7 +15,7 @@
"dependencies": {
"@hexclave/shared": "workspace:*",
"@vercel/mcp-adapter": "^1.0.0",
"next": "16.2.7",
"next": "16.2.9",
"posthog-node": "^4.1.0",
"react": "19.2.3",
"react-dom": "19.2.3",

View File

@ -14,7 +14,7 @@
},
"dependencies": {
"@hexclave/shared": "workspace:*",
"next": "16.2.7",
"next": "16.2.9",
"react": "19.2.3",
"react-dom": "19.2.3"
},

View File

@ -447,7 +447,8 @@ describe("StackClientApp cross-domain auth", () => {
}));
let currentHref = callbackUrl.toString();
let redirectedUrl = "";
const redirectSpy = vi.spyOn(StackClientApp.prototype as any, "_redirectTo").mockImplementation(async (options: { url: string | URL }) => {
const redirectSpy = vi.spyOn(StackClientApp.prototype as any, "_redirectTo").mockImplementation(async (...args: unknown[]) => {
const options = args[0] as { url: string | URL };
redirectedUrl = options.url.toString();
});

View File

@ -195,7 +195,7 @@ importers:
version: 1.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@sentry/nextjs':
specifier: ^10.45.0
version: 10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))
version: 10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))
'@simplewebauthn/server':
specifier: ^13.3.0
version: 13.3.0
@ -248,8 +248,8 @@ importers:
specifier: ^1.0.6
version: 1.0.6
next:
specifier: 16.2.7
version: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: 16.2.9
version: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
nodemailer:
specifier: ^6.9.10
version: 6.9.13
@ -499,7 +499,7 @@ importers:
version: 2.0.2(react@19.2.3)
'@sentry/nextjs':
specifier: ^10.11.0
version: 10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))
version: 10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))
'@stripe/connect-js':
specifier: ^3.3.27
version: 3.3.27
@ -520,10 +520,10 @@ importers:
version: 3.13.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@vercel/analytics':
specifier: ^1.2.2
version: 1.3.1(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
version: 1.3.1(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
'@vercel/speed-insights':
specifier: ^1.0.12
version: 1.0.12(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
version: 1.0.12(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
ai:
specifier: ^6.0.0
version: 6.0.81(zod@4.1.12)
@ -553,7 +553,7 @@ importers:
version: 1.4.0
geist:
specifier: ^1
version: 1.3.0(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
version: 1.3.0(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
input-otp:
specifier: ^1.4.1
version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@ -573,11 +573,11 @@ importers:
specifier: ^12.39.0
version: 12.39.0(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next:
specifier: 16.2.7
version: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: 16.2.9
version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next-themes:
specifier: ^0.2.1
version: 0.2.1(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
version: 0.2.1(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
posthog-js:
specifier: ^1.336.1
version: 1.336.1
@ -915,8 +915,8 @@ importers:
specifier: ^4.1.0
version: 4.1.0
next:
specifier: 16.2.7
version: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: 16.2.9
version: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react:
specifier: 19.2.3
version: 19.2.3
@ -968,10 +968,10 @@ importers:
version: link:../../packages/shared
'@vercel/mcp-adapter':
specifier: ^1.0.0
version: 1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
version: 1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
next:
specifier: 16.2.7
version: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: 16.2.9
version: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
posthog-node:
specifier: ^4.1.0
version: 4.1.0
@ -1032,8 +1032,8 @@ importers:
specifier: workspace:*
version: link:../../packages/shared
next:
specifier: 16.2.7
version: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: 16.2.9
version: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react:
specifier: 19.2.3
version: 19.2.3
@ -1212,7 +1212,7 @@ importers:
devDependencies:
mint:
specifier: ^4.2.487
version: 4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
version: 4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
examples/cjs-test:
dependencies:
@ -1760,10 +1760,10 @@ importers:
version: link:../../packages/next
'@supabase/ssr':
specifier: latest
version: 0.10.3(@supabase/supabase-js@2.108.0)
version: 0.12.0(@supabase/supabase-js@2.108.1)
'@supabase/supabase-js':
specifier: latest
version: 2.108.0
version: 2.108.1
jose:
specifier: ^5.2.2
version: 5.6.3
@ -1993,7 +1993,7 @@ importers:
devDependencies:
'@quetzallabs/i18n':
specifier: ^0.1.19
version: 0.1.19(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))
version: 0.1.19(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))
'@types/color':
specifier: ^3.0.6
version: 3.0.6
@ -2271,7 +2271,7 @@ importers:
devDependencies:
'@quetzallabs/i18n':
specifier: ^0.1.19
version: 0.1.19(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))
version: 0.1.19(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))
'@types/color':
specifier: ^3.0.6
version: 3.0.6
@ -2426,7 +2426,7 @@ importers:
devDependencies:
'@sentry/nextjs':
specifier: ^10.11.0
version: 10.45.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))
version: 10.45.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))
'@simplewebauthn/types':
specifier: ^11.0.0
version: 11.0.0
@ -2560,7 +2560,7 @@ importers:
devDependencies:
'@quetzallabs/i18n':
specifier: ^0.1.19
version: 0.1.19(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
version: 0.1.19(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
'@tanstack/react-router':
specifier: ^1.167.4
version: 1.169.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@ -5759,8 +5759,8 @@ packages:
'@next/env@15.5.19':
resolution: {integrity: sha512-sWWluFvcv5v3Fxznmf2ZfjyoVQt/64oCnYqS90inQWGzMPK1VjvekPiz3OPHKmFT30EnHrjlbyaHLt3M0vWabw==}
'@next/env@16.2.7':
resolution: {integrity: sha512-tMJizPlj6ZYpBMMdK8S0LJufrP4QTdR6pcv9KQ/bVETPAmg0j1mlHE9G2c38UyGHxoBapgwuj7XjbGJ2RcDFOg==}
'@next/env@16.2.9':
resolution: {integrity: sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==}
'@next/eslint-plugin-next@14.2.17':
resolution: {integrity: sha512-fW6/u1jjlBQrMs1ExyINehaK3B+LEW5UqdF6QYL07QK+SECkX0hnEyPMaNKj0ZFzirQ9D8jLWQ00P8oua4yx9g==}
@ -5786,8 +5786,8 @@ packages:
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-arm64@16.2.7':
resolution: {integrity: sha512-vm1EDI/pVaBNNiychmxk3fft+OhQPVD9cIM/tReLZIQ3TfQ4kqI9DwKk00dzuS1ulC7icbrzCFrmRRlk9PfNdw==}
'@next/swc-darwin-arm64@16.2.9':
resolution: {integrity: sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
@ -5804,8 +5804,8 @@ packages:
cpu: [x64]
os: [darwin]
'@next/swc-darwin-x64@16.2.7':
resolution: {integrity: sha512-O3IRSv1ZBL1zs0WrIgefTEcTKFVn+ryxBNe54erJ6KsD+2f/Mmt7g2jOYh8PSBdUwPtKQJuCsTMlZ7tIu2AcsQ==}
'@next/swc-darwin-x64@16.2.9':
resolution: {integrity: sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
@ -5824,8 +5824,8 @@ packages:
os: [linux]
libc: [glibc]
'@next/swc-linux-arm64-gnu@16.2.7':
resolution: {integrity: sha512-Re6PZtjBDd0aMU+VcZcC/PrIvj4WhrjDYtMhhCVQamWN4L90EVP0pcEOBQD25prSlw7OzNw5QpHLWMilRLsRNw==}
'@next/swc-linux-arm64-gnu@16.2.9':
resolution: {integrity: sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@ -5845,8 +5845,8 @@ packages:
os: [linux]
libc: [musl]
'@next/swc-linux-arm64-musl@16.2.7':
resolution: {integrity: sha512-qyogG9QtBzWxgJfeGBvOEHI3851gTfCF3wLZ5RDLTBJGAmE9p1qDwKCOdrBrvBzRvYDT+gUDp72pzlSEfAXgNA==}
'@next/swc-linux-arm64-musl@16.2.9':
resolution: {integrity: sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@ -5866,8 +5866,8 @@ packages:
os: [linux]
libc: [glibc]
'@next/swc-linux-x64-gnu@16.2.7':
resolution: {integrity: sha512-Vhe4ZDuBpmMogrGi5D4R2Kq4JAQlj6+wvgaFYy31zfES0zPmt6TLA+cuYpM/OLrPZjo2MYQTHVqNUSCR6+fDZQ==}
'@next/swc-linux-x64-gnu@16.2.9':
resolution: {integrity: sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@ -5887,8 +5887,8 @@ packages:
os: [linux]
libc: [musl]
'@next/swc-linux-x64-musl@16.2.7':
resolution: {integrity: sha512-srvian89JahFLw1YLBEuhvPJ0DO5lpUeJQMXy4xYo7g628ZlNgXdNkqoxSAv9OYrBfByh6vxISMwW/mRbzCY+g==}
'@next/swc-linux-x64-musl@16.2.9':
resolution: {integrity: sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@ -5906,8 +5906,8 @@ packages:
cpu: [arm64]
os: [win32]
'@next/swc-win32-arm64-msvc@16.2.7':
resolution: {integrity: sha512-GX3wvLpULFuRFJzwHaKfm7QZJ18F4ZSuxlPJ96BoBglCzBmdSjyeBKF+ZhWhvL/ckxNfLnNa7bsObO2ipYpszw==}
'@next/swc-win32-arm64-msvc@16.2.9':
resolution: {integrity: sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
@ -5930,8 +5930,8 @@ packages:
cpu: [x64]
os: [win32]
'@next/swc-win32-x64-msvc@16.2.7':
resolution: {integrity: sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw==}
'@next/swc-win32-x64-msvc@16.2.9':
resolution: {integrity: sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@ -9351,36 +9351,36 @@ packages:
resolution: {integrity: sha512-SXuhqhuR5FXaYgKTXzZJeqtVA6JKb9IZWaGeEUxHHiOcFy2p51wccO72bYpXwoK4D5pzQOIYLTuAc7etxyMmwg==}
engines: {node: '>=12.16'}
'@supabase/auth-js@2.108.0':
resolution: {integrity: sha512-0CzVGVqHfNOhRQVEcAmu58Mex2Ce0zL3aGfyV+iFQjTK6OntLK/hLCLr/VDRX0E8/2CdsiY99L7fZ/8ys/op4w==}
'@supabase/auth-js@2.108.1':
resolution: {integrity: sha512-Lle5rKU8f9LF3K5dDd8Or8mkkG+ptzRZZWKPVMm9B9UuovH65Ss2+iFnQqRsCqaGouvJEcTWyl0cj2riNrrDLQ==}
engines: {node: '>=20.0.0'}
'@supabase/functions-js@2.108.0':
resolution: {integrity: sha512-lqEGDzT7QBUuKYzi5lHpV/XecXT9wikzcbXMbFo6krNpSDynD1sHM8wcsfB/BAqa4NkFuy3vF4JCV8MeakV8IQ==}
'@supabase/functions-js@2.108.1':
resolution: {integrity: sha512-fxBRW/A4IG7ADQztVt0NaEy5ysiO1WJ2pbldsnBchrkHuyepX0Krek9qA9T4gUQBVVTCE9Ea4pdsM5hfn3nc4A==}
engines: {node: '>=20.0.0'}
'@supabase/phoenix@0.4.2':
resolution: {integrity: sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==}
'@supabase/postgrest-js@2.108.0':
resolution: {integrity: sha512-8AwTkPqowDYv/qh016CyXeZ3Ukpw6NHyfqc7DWV4afLR2hAiapf3zRKV2ZLG+//T1LK84HrR6X8VBwfgHWmNyw==}
'@supabase/postgrest-js@2.108.1':
resolution: {integrity: sha512-9lj2MCPPMgSTaJ5y+amnhb3TWPtMFVlbDn2hmX/VV91xQU4j0AauwfMaBErHBJ+zzsSwjc0jLU+zLIZFLQzfig==}
engines: {node: '>=20.0.0'}
'@supabase/realtime-js@2.108.0':
resolution: {integrity: sha512-N3xR0u7TNr+c5wuLSU60rcfu/H/8N0WBs7iHWwjI/NxKwY3XWSyLUbpbpU8bzmL0dA/Gk9Mupri8mxKUXBW+iw==}
'@supabase/realtime-js@2.108.1':
resolution: {integrity: sha512-mHGGqOjwd1XTydcoffUqEMsbFQHUi6A3uhQ0EXr3iqzpLqItxKA9nbN6gIQxrZ7JRRnuUe/iOFPUkYV9Tdc5lg==}
engines: {node: '>=20.0.0'}
'@supabase/ssr@0.10.3':
resolution: {integrity: sha512-ux2CJgX89h0Fz2lY7ZNafNG2SkXpyRc5dz77K9eKeBLPdtywQixKwIuetDeIViAJBp/buOUVmgj8PVesOklNpw==}
'@supabase/ssr@0.12.0':
resolution: {integrity: sha512-d9XV5XzJvzzZbeAIM7fWTCUYxQJZ2Ru6ny3dJHmHGp/LIrJ+o9FpD7N9Rf/UhhWEvHXSoDe8SI32Z2ouOdMjBg==}
peerDependencies:
'@supabase/supabase-js': ^2.105.3
'@supabase/supabase-js': ^2.108.0
'@supabase/storage-js@2.108.0':
resolution: {integrity: sha512-zMYQmh87CId7d8i/1FIfv4fMDcXPutmIJSpoY58GLXi7M266MNzxWGNabDk4i555Oj1Nqtsu2i3Qo3rpWUXO6A==}
'@supabase/storage-js@2.108.1':
resolution: {integrity: sha512-Er0SGGt85iT6ye+SSh98Az6L2CesoZJuyzEZYH2oBOAnIxa9Nn4CtwUC3veGxYggoT56X+3tVuuQeDBP8kR8sg==}
engines: {node: '>=20.0.0'}
'@supabase/supabase-js@2.108.0':
resolution: {integrity: sha512-AjPoimM9MZLZbddnlDBGmpZ/Tas1dNcJvuZy/VD1AfmrjBC8J2RSw6UOqR4ISLLlEioOLca/5t1crFnAxa0wRQ==}
'@supabase/supabase-js@2.108.1':
resolution: {integrity: sha512-V/1hRKLSCJ0zEL+9QFRBUtivvePfOsaAYQmC0HhFNSHC2F3xFs4jSF3YhkLmzex6E4V4FGvmBDOP72D/53NnZA==}
engines: {node: '>=20.0.0'}
'@swc/counter@0.1.3':
@ -15268,8 +15268,8 @@ packages:
sass:
optional: true
next@16.2.7:
resolution: {integrity: sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w==}
next@16.2.9:
resolution: {integrity: sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==}
engines: {node: '>=20.9.0'}
hasBin: true
peerDependencies:
@ -22174,13 +22174,13 @@ snapshots:
dependencies:
'@chevrotain/types': 11.1.2
'@mintlify/cli@4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/cli@4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@inquirer/prompts': 7.9.0(@types/node@20.17.6)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/link-rot': 3.0.1010(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/previewing': 4.0.1038(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/link-rot': 3.0.1010(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/previewing': 4.0.1038(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
adm-zip: 0.5.16
chalk: 5.2.0
@ -22280,7 +22280,7 @@ snapshots:
- ts-node
- typescript
'@mintlify/common@1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/common@1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@asyncapi/parser': 3.4.0
'@asyncapi/specs': 6.8.1
@ -22322,7 +22322,7 @@ snapshots:
remark-rehype: 11.1.1
remark-stringify: 11.0.0
sucrase: 3.35.0
tailwindcss: 3.4.18(tsx@4.19.3)(yaml@2.6.0)
tailwindcss: 3.4.18(tsx@4.21.0)(yaml@2.8.0)
unified: 11.0.5
unist-builder: 4.0.0
unist-util-map: 4.0.0
@ -22344,7 +22344,7 @@ snapshots:
- typescript
- yaml
'@mintlify/common@1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/common@1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@asyncapi/parser': 3.4.0
'@asyncapi/specs': 6.8.1
@ -22386,7 +22386,7 @@ snapshots:
remark-rehype: 11.1.1
remark-stringify: 11.0.0
sucrase: 3.35.0
tailwindcss: 3.4.18(tsx@4.19.3)(yaml@2.6.0)
tailwindcss: 3.4.18(tsx@4.21.0)(yaml@2.8.0)
unified: 11.0.5
unist-builder: 4.0.0
unist-util-map: 4.0.0
@ -22408,11 +22408,11 @@ snapshots:
- typescript
- yaml
'@mintlify/link-rot@3.0.1010(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/link-rot@3.0.1010(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/previewing': 4.0.1038(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/previewing': 4.0.1038(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/scraping': 4.0.522(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(typescript@5.9.3)
'@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(typescript@5.9.3)
fs-extra: 11.1.0
@ -22510,11 +22510,11 @@ snapshots:
leven: 4.0.0
yaml: 2.8.0
'@mintlify/prebuild@1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/prebuild@1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/openapi-parser': 0.0.8
'@mintlify/scraping': 4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/scraping': 4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(typescript@5.9.3)
chalk: 5.3.0
favicons: 7.2.0
@ -22542,11 +22542,11 @@ snapshots:
- utf-8-validate
- yaml
'@mintlify/prebuild@1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/prebuild@1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/openapi-parser': 0.0.8
'@mintlify/scraping': 4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/scraping': 4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
chalk: 5.3.0
favicons: 7.2.0
@ -22574,10 +22574,10 @@ snapshots:
- utf-8-validate
- yaml
'@mintlify/previewing@4.0.1038(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/previewing@4.0.1038(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
adm-zip: 0.5.16
better-opn: 3.0.2
@ -22647,9 +22647,9 @@ snapshots:
- typescript
- utf-8-validate
'@mintlify/scraping@4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/scraping@4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@18.3.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/openapi-parser': 0.0.8
fs-extra: 11.1.1
hast-util-to-mdast: 10.1.0
@ -22682,9 +22682,9 @@ snapshots:
- utf-8-validate
- yaml
'@mintlify/scraping@4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)':
'@mintlify/scraping@4.0.699(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)':
dependencies:
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
'@mintlify/openapi-parser': 0.0.8
fs-extra: 11.1.1
hast-util-to-mdast: 10.1.0
@ -22834,7 +22834,7 @@ snapshots:
'@next/env@15.5.19': {}
'@next/env@16.2.7': {}
'@next/env@16.2.9': {}
'@next/eslint-plugin-next@14.2.17':
dependencies:
@ -22858,7 +22858,7 @@ snapshots:
'@next/swc-darwin-arm64@15.5.19':
optional: true
'@next/swc-darwin-arm64@16.2.7':
'@next/swc-darwin-arm64@16.2.9':
optional: true
'@next/swc-darwin-x64@14.2.33':
@ -22867,7 +22867,7 @@ snapshots:
'@next/swc-darwin-x64@15.5.19':
optional: true
'@next/swc-darwin-x64@16.2.7':
'@next/swc-darwin-x64@16.2.9':
optional: true
'@next/swc-linux-arm64-gnu@14.2.33':
@ -22876,7 +22876,7 @@ snapshots:
'@next/swc-linux-arm64-gnu@15.5.19':
optional: true
'@next/swc-linux-arm64-gnu@16.2.7':
'@next/swc-linux-arm64-gnu@16.2.9':
optional: true
'@next/swc-linux-arm64-musl@14.2.33':
@ -22885,7 +22885,7 @@ snapshots:
'@next/swc-linux-arm64-musl@15.5.19':
optional: true
'@next/swc-linux-arm64-musl@16.2.7':
'@next/swc-linux-arm64-musl@16.2.9':
optional: true
'@next/swc-linux-x64-gnu@14.2.33':
@ -22894,7 +22894,7 @@ snapshots:
'@next/swc-linux-x64-gnu@15.5.19':
optional: true
'@next/swc-linux-x64-gnu@16.2.7':
'@next/swc-linux-x64-gnu@16.2.9':
optional: true
'@next/swc-linux-x64-musl@14.2.33':
@ -22903,7 +22903,7 @@ snapshots:
'@next/swc-linux-x64-musl@15.5.19':
optional: true
'@next/swc-linux-x64-musl@16.2.7':
'@next/swc-linux-x64-musl@16.2.9':
optional: true
'@next/swc-win32-arm64-msvc@14.2.33':
@ -22912,7 +22912,7 @@ snapshots:
'@next/swc-win32-arm64-msvc@15.5.19':
optional: true
'@next/swc-win32-arm64-msvc@16.2.7':
'@next/swc-win32-arm64-msvc@16.2.9':
optional: true
'@next/swc-win32-ia32-msvc@14.2.33':
@ -22924,7 +22924,7 @@ snapshots:
'@next/swc-win32-x64-msvc@15.5.19':
optional: true
'@next/swc-win32-x64-msvc@16.2.7':
'@next/swc-win32-x64-msvc@16.2.9':
optional: true
'@node-oauth/formats@1.0.0': {}
@ -24438,7 +24438,7 @@ snapshots:
- next
- supports-color
'@quetzallabs/i18n@0.1.19(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))':
'@quetzallabs/i18n@0.1.19(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))':
dependencies:
'@babel/parser': 7.29.0
'@babel/traverse': 7.29.0
@ -24446,7 +24446,7 @@ snapshots:
dotenv: 10.0.0
i18next: 21.10.0
i18next-parser: 9.0.2
next-intl: 3.19.1(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@18.3.1)
next-intl: 3.19.1(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@18.3.1)
path: 0.12.7
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@ -24456,7 +24456,7 @@ snapshots:
- next
- supports-color
'@quetzallabs/i18n@0.1.19(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))':
'@quetzallabs/i18n@0.1.19(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))':
dependencies:
'@babel/parser': 7.29.0
'@babel/traverse': 7.29.0
@ -24464,7 +24464,7 @@ snapshots:
dotenv: 10.0.0
i18next: 21.10.0
i18next-parser: 9.0.2
next-intl: 3.19.1(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))(react@18.3.1)
next-intl: 3.19.1(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))(react@18.3.1)
path: 0.12.7
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@ -24474,7 +24474,7 @@ snapshots:
- next
- supports-color
'@quetzallabs/i18n@0.1.19(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
'@quetzallabs/i18n@0.1.19(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
dependencies:
'@babel/parser': 7.29.0
'@babel/traverse': 7.29.0
@ -24482,7 +24482,7 @@ snapshots:
dotenv: 10.0.0
i18next: 21.10.0
i18next-parser: 9.0.2
next-intl: 3.19.1(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@18.3.1)
next-intl: 3.19.1(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@18.3.1)
path: 0.12.7
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@ -27090,7 +27090,7 @@ snapshots:
'@sentry/core@10.45.0': {}
'@sentry/nextjs@10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))':
'@sentry/nextjs@10.11.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.37.0
@ -27104,7 +27104,7 @@ snapshots:
'@sentry/vercel-edge': 10.11.0
'@sentry/webpack-plugin': 4.3.0(webpack@5.92.0(esbuild@0.24.2))
chalk: 3.0.0
next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
resolve: 1.22.8
rollup: 4.50.1
stacktrace-parser: 0.1.11
@ -27117,7 +27117,7 @@ snapshots:
- supports-color
- webpack
'@sentry/nextjs@10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))':
'@sentry/nextjs@10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.40.0
@ -27130,7 +27130,7 @@ snapshots:
'@sentry/react': 10.45.0(react@19.2.3)
'@sentry/vercel-edge': 10.45.0
'@sentry/webpack-plugin': 5.1.1(webpack@5.92.0(esbuild@0.24.2))
next: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
rollup: 4.57.1
stacktrace-parser: 0.1.11
transitivePeerDependencies:
@ -27142,7 +27142,7 @@ snapshots:
- supports-color
- webpack
'@sentry/nextjs@10.45.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))':
'@sentry/nextjs@10.45.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.40.0
@ -27155,7 +27155,7 @@ snapshots:
'@sentry/react': 10.45.0(react@19.2.3)
'@sentry/vercel-edge': 10.45.0
'@sentry/webpack-plugin': 5.1.1(webpack@5.92.0(esbuild@0.24.2))
next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
rollup: 4.57.1
stacktrace-parser: 0.1.11
transitivePeerDependencies:
@ -28249,42 +28249,42 @@ snapshots:
'@stripe/stripe-js@7.7.0': {}
'@supabase/auth-js@2.108.0':
'@supabase/auth-js@2.108.1':
dependencies:
tslib: 2.8.1
'@supabase/functions-js@2.108.0':
'@supabase/functions-js@2.108.1':
dependencies:
tslib: 2.8.1
'@supabase/phoenix@0.4.2': {}
'@supabase/postgrest-js@2.108.0':
'@supabase/postgrest-js@2.108.1':
dependencies:
tslib: 2.8.1
'@supabase/realtime-js@2.108.0':
'@supabase/realtime-js@2.108.1':
dependencies:
'@supabase/phoenix': 0.4.2
tslib: 2.8.1
'@supabase/ssr@0.10.3(@supabase/supabase-js@2.108.0)':
'@supabase/ssr@0.12.0(@supabase/supabase-js@2.108.1)':
dependencies:
'@supabase/supabase-js': 2.108.0
'@supabase/supabase-js': 2.108.1
cookie: 1.0.2
'@supabase/storage-js@2.108.0':
'@supabase/storage-js@2.108.1':
dependencies:
iceberg-js: 0.8.1
tslib: 2.8.1
'@supabase/supabase-js@2.108.0':
'@supabase/supabase-js@2.108.1':
dependencies:
'@supabase/auth-js': 2.108.0
'@supabase/functions-js': 2.108.0
'@supabase/postgrest-js': 2.108.0
'@supabase/realtime-js': 2.108.0
'@supabase/storage-js': 2.108.0
'@supabase/auth-js': 2.108.1
'@supabase/functions-js': 2.108.1
'@supabase/postgrest-js': 2.108.1
'@supabase/realtime-js': 2.108.1
'@supabase/storage-js': 2.108.1
'@swc/counter@0.1.3': {}
@ -29807,23 +29807,23 @@ snapshots:
jose: 5.6.3
neverthrow: 7.2.0
'@vercel/analytics@1.3.1(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
'@vercel/analytics@1.3.1(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
dependencies:
server-only: 0.0.1
optionalDependencies:
next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
'@vercel/functions@2.0.0(@aws-sdk/credential-provider-web-identity@3.972.27)':
optionalDependencies:
'@aws-sdk/credential-provider-web-identity': 3.972.27
'@vercel/mcp-adapter@1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
'@vercel/mcp-adapter@1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
dependencies:
'@modelcontextprotocol/sdk': 1.17.2
mcp-handler: 1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
mcp-handler: 1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
optionalDependencies:
next: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@vercel/oidc@3.1.0': {}
@ -29849,9 +29849,9 @@ snapshots:
xdg-app-paths: 5.1.0
zod: 3.24.4
'@vercel/speed-insights@1.0.12(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
'@vercel/speed-insights@1.0.12(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
optionalDependencies:
next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
'@vitejs/plugin-react@4.3.3(vite@7.3.1(@types/node@20.17.6)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.19.3)(yaml@2.6.0))':
@ -33691,9 +33691,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
geist@1.3.0(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)):
geist@1.3.0(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)):
dependencies:
next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
generate-function@2.3.1:
dependencies:
@ -35364,14 +35364,14 @@ snapshots:
math-intrinsics@1.1.0: {}
mcp-handler@1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)):
mcp-handler@1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)):
dependencies:
'@modelcontextprotocol/sdk': 1.17.2
chalk: 5.6.2
commander: 11.1.0
redis: 4.7.1
optionalDependencies:
next: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
mdast-util-find-and-replace@3.0.2:
dependencies:
@ -36018,9 +36018,9 @@ snapshots:
dependencies:
minipass: 7.1.3
mint@4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0):
mint@4.2.487(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0):
dependencies:
'@mintlify/cli': 4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.19.3)(typescript@5.9.3)(yaml@2.6.0)
'@mintlify/cli': 4.0.1090(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@20.17.6)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.0)
transitivePeerDependencies:
- '@radix-ui/react-popover'
- '@types/node'
@ -36155,27 +36155,27 @@ snapshots:
react: 18.3.1
use-intl: 3.19.1(react@18.3.1)
next-intl@3.19.1(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@18.3.1):
next-intl@3.19.1(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@18.3.1):
dependencies:
'@formatjs/intl-localematcher': 0.5.4
negotiator: 0.6.4
next: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
next: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
react: 18.3.1
use-intl: 3.19.1(react@18.3.1)
next-intl@3.19.1(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))(react@18.3.1):
next-intl@3.19.1(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1))(react@18.3.1):
dependencies:
'@formatjs/intl-localematcher': 0.5.4
negotiator: 0.6.4
next: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1)
next: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1)
react: 18.3.1
use-intl: 3.19.1(react@18.3.1)
next-intl@3.19.1(next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@18.3.1):
next-intl@3.19.1(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@18.3.1):
dependencies:
'@formatjs/intl-localematcher': 0.5.4
negotiator: 0.6.4
next: 16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 18.3.1
use-intl: 3.19.1(react@18.3.1)
@ -36217,9 +36217,9 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
next-themes@0.2.1(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
next-themes@0.2.1(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@ -36388,9 +36388,9 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
dependencies:
'@next/env': 16.2.7
'@next/env': 16.2.9
'@swc/helpers': 0.5.15
baseline-browser-mapping: 2.10.16
caniuse-lite: 1.0.30001751
@ -36399,23 +36399,23 @@ snapshots:
react-dom: 19.2.1(react@19.2.1)
styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.2.1)
optionalDependencies:
'@next/swc-darwin-arm64': 16.2.7
'@next/swc-darwin-x64': 16.2.7
'@next/swc-linux-arm64-gnu': 16.2.7
'@next/swc-linux-arm64-musl': 16.2.7
'@next/swc-linux-x64-gnu': 16.2.7
'@next/swc-linux-x64-musl': 16.2.7
'@next/swc-win32-arm64-msvc': 16.2.7
'@next/swc-win32-x64-msvc': 16.2.7
'@next/swc-darwin-arm64': 16.2.9
'@next/swc-darwin-x64': 16.2.9
'@next/swc-linux-arm64-gnu': 16.2.9
'@next/swc-linux-arm64-musl': 16.2.9
'@next/swc-linux-x64-gnu': 16.2.9
'@next/swc-linux-x64-musl': 16.2.9
'@next/swc-win32-arm64-msvc': 16.2.9
'@next/swc-win32-x64-msvc': 16.2.9
'@opentelemetry/api': 1.9.0
sharp: 0.34.5
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1):
next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.1))(react@19.2.1):
dependencies:
'@next/env': 16.2.7
'@next/env': 16.2.9
'@swc/helpers': 0.5.15
baseline-browser-mapping: 2.10.16
caniuse-lite: 1.0.30001751
@ -36424,23 +36424,23 @@ snapshots:
react-dom: 19.2.3(react@19.2.1)
styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.2.1)
optionalDependencies:
'@next/swc-darwin-arm64': 16.2.7
'@next/swc-darwin-x64': 16.2.7
'@next/swc-linux-arm64-gnu': 16.2.7
'@next/swc-linux-arm64-musl': 16.2.7
'@next/swc-linux-x64-gnu': 16.2.7
'@next/swc-linux-x64-musl': 16.2.7
'@next/swc-win32-arm64-msvc': 16.2.7
'@next/swc-win32-x64-msvc': 16.2.7
'@next/swc-darwin-arm64': 16.2.9
'@next/swc-darwin-x64': 16.2.9
'@next/swc-linux-arm64-gnu': 16.2.9
'@next/swc-linux-arm64-musl': 16.2.9
'@next/swc-linux-x64-gnu': 16.2.9
'@next/swc-linux-x64-musl': 16.2.9
'@next/swc-win32-arm64-msvc': 16.2.9
'@next/swc-win32-x64-msvc': 16.2.9
'@opentelemetry/api': 1.9.0
sharp: 0.34.5
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
next@16.2.7(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
'@next/env': 16.2.7
'@next/env': 16.2.9
'@swc/helpers': 0.5.15
baseline-browser-mapping: 2.10.16
caniuse-lite: 1.0.30001751
@ -36449,23 +36449,23 @@ snapshots:
react-dom: 19.2.3(react@19.2.3)
styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.2.3)
optionalDependencies:
'@next/swc-darwin-arm64': 16.2.7
'@next/swc-darwin-x64': 16.2.7
'@next/swc-linux-arm64-gnu': 16.2.7
'@next/swc-linux-arm64-musl': 16.2.7
'@next/swc-linux-x64-gnu': 16.2.7
'@next/swc-linux-x64-musl': 16.2.7
'@next/swc-win32-arm64-msvc': 16.2.7
'@next/swc-win32-x64-msvc': 16.2.7
'@next/swc-darwin-arm64': 16.2.9
'@next/swc-darwin-x64': 16.2.9
'@next/swc-linux-arm64-gnu': 16.2.9
'@next/swc-linux-arm64-musl': 16.2.9
'@next/swc-linux-x64-gnu': 16.2.9
'@next/swc-linux-x64-musl': 16.2.9
'@next/swc-win32-arm64-msvc': 16.2.9
'@next/swc-win32-x64-msvc': 16.2.9
'@opentelemetry/api': 1.9.0
sharp: 0.34.5
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
'@next/env': 16.2.7
'@next/env': 16.2.9
'@swc/helpers': 0.5.15
baseline-browser-mapping: 2.10.16
caniuse-lite: 1.0.30001751
@ -36474,14 +36474,14 @@ snapshots:
react-dom: 19.2.3(react@19.2.3)
styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.3)
optionalDependencies:
'@next/swc-darwin-arm64': 16.2.7
'@next/swc-darwin-x64': 16.2.7
'@next/swc-linux-arm64-gnu': 16.2.7
'@next/swc-linux-arm64-musl': 16.2.7
'@next/swc-linux-x64-gnu': 16.2.7
'@next/swc-linux-x64-musl': 16.2.7
'@next/swc-win32-arm64-msvc': 16.2.7
'@next/swc-win32-x64-msvc': 16.2.7
'@next/swc-darwin-arm64': 16.2.9
'@next/swc-darwin-x64': 16.2.9
'@next/swc-linux-arm64-gnu': 16.2.9
'@next/swc-linux-arm64-musl': 16.2.9
'@next/swc-linux-x64-gnu': 16.2.9
'@next/swc-linux-x64-musl': 16.2.9
'@next/swc-win32-arm64-msvc': 16.2.9
'@next/swc-win32-x64-msvc': 16.2.9
'@opentelemetry/api': 1.9.0
sharp: 0.34.5
transitivePeerDependencies:
@ -37196,15 +37196,6 @@ snapshots:
optionalDependencies:
postcss: 8.5.6
postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.19.3)(yaml@2.6.0):
dependencies:
lilconfig: 3.1.3
optionalDependencies:
jiti: 1.21.7
postcss: 8.5.6
tsx: 4.19.3
yaml: 2.6.0
postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.0):
dependencies:
lilconfig: 3.1.3
@ -39532,34 +39523,6 @@ snapshots:
transitivePeerDependencies:
- ts-node
tailwindcss@3.4.18(tsx@4.19.3)(yaml@2.6.0):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
chokidar: 3.6.0
didyoumean: 1.2.2
dlv: 1.1.3
fast-glob: 3.3.3
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.21.7
lilconfig: 3.1.3
micromatch: 4.0.8
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.1.1
postcss: 8.5.6
postcss-import: 15.1.0(postcss@8.5.6)
postcss-js: 4.0.1(postcss@8.5.6)
postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.19.3)(yaml@2.6.0)
postcss-nested: 6.2.0(postcss@8.5.6)
postcss-selector-parser: 6.1.2
resolve: 1.22.11
sucrase: 3.35.0
transitivePeerDependencies:
- tsx
- yaml
tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.0):
dependencies:
'@alloc/quick-lru': 5.2.0