From ccabc319359582d229c8e63105073206aea0ebe7 Mon Sep 17 00:00:00 2001 From: 0xKoller Date: Wed, 5 Nov 2025 12:17:16 -0300 Subject: [PATCH 1/7] feat: added stats page --- apps/website/app/api/npm/downloads/route.ts | 81 ++ apps/website/app/api/npm/metadata/route.ts | 54 + apps/website/app/api/npm/search/route.ts | 39 + apps/website/app/api/npm/versions/route.ts | 41 + apps/website/app/sitemap.ts | 2 +- apps/website/app/stats/opengraph-image.tsx | 236 ++++ apps/website/app/stats/page.tsx | 490 ++++++++ apps/website/basehub-types.d.ts | 1054 ++--------------- .../dashboard/download-trend-chart.tsx | 117 ++ .../components/dashboard/github-stats.tsx | 117 ++ .../components/dashboard/metrics-cards.tsx | 134 +++ .../components/dashboard/package-info.tsx | 293 +++++ .../dashboard/version-distribution-chart.tsx | 154 +++ apps/website/components/ui/badge.tsx | 46 + apps/website/components/ui/card.tsx | 92 ++ apps/website/components/ui/table.tsx | 116 ++ apps/website/components/ui/tabs.tsx | 66 ++ apps/website/lib/types/npm.ts | 195 +++ apps/website/package.json | 2 + 19 files changed, 2360 insertions(+), 969 deletions(-) create mode 100644 apps/website/app/api/npm/downloads/route.ts create mode 100644 apps/website/app/api/npm/metadata/route.ts create mode 100644 apps/website/app/api/npm/search/route.ts create mode 100644 apps/website/app/api/npm/versions/route.ts create mode 100644 apps/website/app/stats/opengraph-image.tsx create mode 100644 apps/website/app/stats/page.tsx create mode 100644 apps/website/components/dashboard/download-trend-chart.tsx create mode 100644 apps/website/components/dashboard/github-stats.tsx create mode 100644 apps/website/components/dashboard/metrics-cards.tsx create mode 100644 apps/website/components/dashboard/package-info.tsx create mode 100644 apps/website/components/dashboard/version-distribution-chart.tsx create mode 100644 apps/website/components/ui/badge.tsx create mode 100644 apps/website/components/ui/card.tsx create mode 100644 apps/website/components/ui/table.tsx create mode 100644 apps/website/components/ui/tabs.tsx create mode 100644 apps/website/lib/types/npm.ts diff --git a/apps/website/app/api/npm/downloads/route.ts b/apps/website/app/api/npm/downloads/route.ts new file mode 100644 index 00000000..4a25cc3a --- /dev/null +++ b/apps/website/app/api/npm/downloads/route.ts @@ -0,0 +1,81 @@ +import { NextResponse } from 'next/server'; +import type { DownloadPoint, DownloadRange } from '@/lib/types/npm'; + +const PACKAGE_NAME = 'xmcp'; +const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads'; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const period = searchParams.get('period') || 'last-year'; + + try { + // Fetch data based on period + let endpoint: string; + + // Check if period is a date range (contains ':') or a range keyword + const isRange = period.includes(':') || period === 'last-year'; + + if (isRange) { + endpoint = `${NPM_DOWNLOADS_API}/range/${period}/${PACKAGE_NAME}`; + } else { + endpoint = `${NPM_DOWNLOADS_API}/point/${period}/${PACKAGE_NAME}`; + } + + const response = await fetch(endpoint, { + next: { revalidate: 3600 }, // Cache for 1 hour + }); + + if (!response.ok) { + throw new Error(`Failed to fetch downloads: ${response.statusText}`); + } + + const data: DownloadPoint | DownloadRange = await response.json(); + + return NextResponse.json(data); + } catch (error) { + console.error('Error fetching download statistics:', error); + return NextResponse.json( + { error: 'Failed to fetch download statistics' }, + { status: 500 } + ); + } +} + +// Fetch multiple periods at once +export async function POST(request: Request) { + try { + const { periods } = await request.json(); + + const promises = periods.map(async (period: string) => { + const isRange = period.includes(':') || period === 'last-year'; + const endpoint = isRange + ? `${NPM_DOWNLOADS_API}/range/${period}/${PACKAGE_NAME}` + : `${NPM_DOWNLOADS_API}/point/${period}/${PACKAGE_NAME}`; + + const response = await fetch(endpoint, { + next: { revalidate: 3600 }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch ${period}`); + } + + return { period, data: await response.json() }; + }); + + const results = await Promise.all(promises); + + return NextResponse.json( + results.reduce((acc, { period, data }) => ({ + ...acc, + [period]: data, + }), {}) + ); + } catch (error) { + console.error('Error fetching multiple download statistics:', error); + return NextResponse.json( + { error: 'Failed to fetch download statistics' }, + { status: 500 } + ); + } +} diff --git a/apps/website/app/api/npm/metadata/route.ts b/apps/website/app/api/npm/metadata/route.ts new file mode 100644 index 00000000..3863c919 --- /dev/null +++ b/apps/website/app/api/npm/metadata/route.ts @@ -0,0 +1,54 @@ +import { NextResponse } from 'next/server'; +import type { PackageMetadata } from '@/lib/types/npm'; + +const PACKAGE_NAME = 'xmcp'; +const NPM_REGISTRY_API = 'https://registry.npmjs.org'; + +export async function GET() { + try { + const response = await fetch( + `${NPM_REGISTRY_API}/${PACKAGE_NAME}`, + { + next: { revalidate: 3600 }, // Cache for 1 hour + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch package metadata: ${response.statusText}`); + } + + const data: PackageMetadata = await response.json(); + + // Extract useful summary information + const latestVersion = data['dist-tags']?.latest || ''; + const latestVersionData = latestVersion ? data.versions[latestVersion] : null; + const versionsCount = Object.keys(data.versions).length; + const lastPublished = data.time?.[latestVersion] || ''; + + return NextResponse.json({ + name: data.name, + description: data.description, + latestVersion, + versionsCount, + lastPublished, + license: latestVersionData?.license || data.license, + author: data.author, + maintainers: data.maintainers, + keywords: data.keywords, + repository: data.repository, + homepage: data.homepage, + bugs: data.bugs, + unpackedSize: latestVersionData?.dist?.unpackedSize, + fileCount: latestVersionData?.dist?.fileCount, + dependencies: latestVersionData?.dependencies, + devDependencies: latestVersionData?.devDependencies, + peerDependencies: latestVersionData?.peerDependencies, + }); + } catch (error) { + console.error('Error fetching package metadata:', error); + return NextResponse.json( + { error: 'Failed to fetch package metadata' }, + { status: 500 } + ); + } +} diff --git a/apps/website/app/api/npm/search/route.ts b/apps/website/app/api/npm/search/route.ts new file mode 100644 index 00000000..251fe937 --- /dev/null +++ b/apps/website/app/api/npm/search/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from 'next/server'; +import type { SearchResponse } from '@/lib/types/npm'; + +const PACKAGE_NAME = 'xmcp'; +const NPM_SEARCH_API = 'https://registry.npmjs.org/-/v1/search'; + +export async function GET() { + try { + const response = await fetch( + `${NPM_SEARCH_API}?text=${PACKAGE_NAME}&size=1`, + { + next: { revalidate: 3600 }, // Cache for 1 hour + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch search data: ${response.statusText}`); + } + + const data: SearchResponse = await response.json(); + + // Find exact package match + const packageResult = data.objects.find( + (obj) => obj.package.name === PACKAGE_NAME + ); + + if (!packageResult) { + throw new Error('Package not found in search results'); + } + + return NextResponse.json(packageResult); + } catch (error) { + console.error('Error fetching search data:', error); + return NextResponse.json( + { error: 'Failed to fetch search data' }, + { status: 500 } + ); + } +} diff --git a/apps/website/app/api/npm/versions/route.ts b/apps/website/app/api/npm/versions/route.ts new file mode 100644 index 00000000..f3c11b7a --- /dev/null +++ b/apps/website/app/api/npm/versions/route.ts @@ -0,0 +1,41 @@ +import { NextResponse } from 'next/server'; +import type { VersionDownloads } from '@/lib/types/npm'; + +const PACKAGE_NAME = 'xmcp'; +const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads'; + +export async function GET() { + try { + const response = await fetch( + `${NPM_DOWNLOADS_API}/point/last-week/${PACKAGE_NAME}`, + { + next: { revalidate: 3600 }, // Cache for 1 hour + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch version downloads: ${response.statusText}`); + } + + const data: VersionDownloads = await response.json(); + + // Sort versions by download count (descending) + const sortedDownloads = Object.entries(data.downloads || {}) + .sort(([, a], [, b]) => b - a) + .reduce((acc, [version, count]) => ({ + ...acc, + [version]: count, + }), {}); + + return NextResponse.json({ + ...data, + downloads: sortedDownloads, + }); + } catch (error) { + console.error('Error fetching version downloads:', error); + return NextResponse.json( + { error: 'Failed to fetch version downloads' }, + { status: 500 } + ); + } +} diff --git a/apps/website/app/sitemap.ts b/apps/website/app/sitemap.ts index 8ab39b49..eb71dca6 100644 --- a/apps/website/app/sitemap.ts +++ b/apps/website/app/sitemap.ts @@ -9,7 +9,7 @@ export const revalidate = false; export default async function sitemap() { const url = (path: string): string => new URL(path, baseUrl).toString(); - const routes = ["", "/docs", "/blog", "/examples", "/x", "/showcase"].map( + const routes = ["", "/docs", "/blog", "/examples", "/x", "/showcase", "/stats"].map( (route) => ({ url: url(route), lastModified: new Date().toISOString().split("T")[0], diff --git a/apps/website/app/stats/opengraph-image.tsx b/apps/website/app/stats/opengraph-image.tsx new file mode 100644 index 00000000..29666294 --- /dev/null +++ b/apps/website/app/stats/opengraph-image.tsx @@ -0,0 +1,236 @@ +import { ImageResponse } from 'next/og'; + +export const runtime = 'edge'; +export const alt = 'xmcp - Weekly NPM Package Statistics'; +export const size = { + width: 1200, + height: 630, +}; +export const contentType = 'image/png'; + +interface DailyDownload { + downloads: number; + day: string; +} + +interface DownloadRange { + downloads: Array; + start: string; + end: string; + package: string; +} + +// Fetch last 7 days of download data +async function getWeeklyData(): Promise<{ total: number; dailyData: DailyDownload[] }> { + try { + const response = await fetch( + 'https://api.npmjs.org/downloads/range/last-week/xmcp', + { next: { revalidate: 3600 }, cache: 'force-cache' } + ); + + if (!response.ok) { + throw new Error('Failed to fetch download data'); + } + + const data: DownloadRange = await response.json(); + const total = data.downloads.reduce((sum, day) => sum + day.downloads, 0); + + return { + total, + dailyData: data.downloads, + }; + } catch (error) { + console.error('Error fetching weekly data:', error); + return { + total: 0, + dailyData: [], + }; + } +} + +export default async function Image() { + const { total, dailyData } = await getWeeklyData(); + + // Normalize data for chart visualization (0-100 scale) + const maxDownloads = Math.max(...dailyData.map(d => d.downloads), 1); + const normalizedData = dailyData.map(day => ({ + ...day, + heightPercent: (day.downloads / maxDownloads) * 100, + })); + + return new ImageResponse( + ( +
+ {/* Left Column - Logo and Download Count */} +
+ {/* xmcp SVG Logo */} + + + + + + + + {/* Weekly downloads metric */} +
+
+ {total.toLocaleString()} +
+
+ Weekly Downloads +
+
+
+ + {/* Right Column - Chart */} +
+ {/* Chart container */} +
+ {/* Line path using SVG */} + + {/* Area gradient fill */} + + + + + + + + {/* Generate smooth path using Catmull-Rom interpolation */} + { + const points = normalizedData.map((day, i) => ({ + x: 20 + (i * (660 / (normalizedData.length - 1))), + y: 530 - Math.max(day.heightPercent * 4.5, 50), + })); + + // Generate smooth curve using quadratic bezier curves + let path = `M ${points[0].x} ${points[0].y}`; + + for (let i = 0; i < points.length - 1; i++) { + const current = points[i]; + const next = points[i + 1]; + const controlX = (current.x + next.x) / 2; + const controlY = (current.y + next.y) / 2; + + path += ` Q ${current.x} ${current.y}, ${controlX} ${controlY}`; + } + + // Complete the last segment + const last = points[points.length - 1]; + const secondLast = points[points.length - 2]; + path += ` Q ${secondLast.x} ${secondLast.y}, ${last.x} ${last.y}`; + + // Close area path + path += ` L ${680} 530 L ${20} 530 Z`; + + return path; + })()} + fill="url(#areaGradient)" + /> + + {/* Smooth line stroke */} + { + const points = normalizedData.map((day, i) => ({ + x: 20 + (i * (660 / (normalizedData.length - 1))), + y: 530 - Math.max(day.heightPercent * 4.5, 50), + })); + + let path = `M ${points[0].x} ${points[0].y}`; + + for (let i = 0; i < points.length - 1; i++) { + const current = points[i]; + const next = points[i + 1]; + const controlX = (current.x + next.x) / 2; + const controlY = (current.y + next.y) / 2; + + path += ` Q ${current.x} ${current.y}, ${controlX} ${controlY}`; + } + + const last = points[points.length - 1]; + const secondLast = points[points.length - 2]; + path += ` Q ${secondLast.x} ${secondLast.y}, ${last.x} ${last.y}`; + + return path; + })()} + stroke="#ffffff" + strokeWidth="5" + fill="none" + strokeLinecap="round" + strokeLinejoin="round" + /> + +
+
+
+ ), + { + ...size, + } + ); +} diff --git a/apps/website/app/stats/page.tsx b/apps/website/app/stats/page.tsx new file mode 100644 index 00000000..db448c44 --- /dev/null +++ b/apps/website/app/stats/page.tsx @@ -0,0 +1,490 @@ +import { Suspense } from 'react'; +import { MetricsCards } from '@/components/dashboard/metrics-cards'; +import { DownloadTrendChart } from '@/components/dashboard/download-trend-chart'; +import { VersionDistributionChart } from '@/components/dashboard/version-distribution-chart'; +import { PackageInfo } from '@/components/dashboard/package-info'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardHeader, CardContent } from '@/components/ui/card'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; +import type { DownloadPoint, DownloadRange, VersionDownloads } from '@/lib/types/npm'; + + +export const revalidate = 3600; + +async function getDashboardData() { + const PACKAGE_NAME = 'xmcp'; + const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads'; + const NPM_REGISTRY_API = 'https://registry.npmjs.org'; + + const formatDate = (date: Date) => { + return date.toISOString().split('T')[0]; + }; + + // Helper function to create 18-month date chunks + const createDateChunks = (startDate: Date, endDate: Date) => { + const chunks: Array<{ start: string; end: string }> = []; + const current = new Date(startDate); + + while (current < endDate) { + const chunkEnd = new Date(current); + chunkEnd.setMonth(chunkEnd.getMonth() + 18); + + if (chunkEnd > endDate) { + chunkEnd.setTime(endDate.getTime()); + } + + chunks.push({ + start: formatDate(current), + end: formatDate(chunkEnd) + }); + + // Move to next day after chunk end + current.setTime(chunkEnd.getTime()); + current.setDate(current.getDate() + 1); + } + + return chunks; + }; + + try { + const metadataRes = await fetch(`${NPM_REGISTRY_API}/${PACKAGE_NAME}`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }); + + if (!metadataRes.ok) { + throw new Error('Failed to fetch package metadata'); + } + + const rawMetadata = await metadataRes.json(); + + const createdDate = rawMetadata.time?.created; + if (!createdDate) { + throw new Error('Package creation date not found'); + } + + + const endDate = new Date(); + endDate.setDate(endDate.getDate() - 1); // Set to yesterday to exclude today's incomplete data + const startDate = new Date(createdDate); + + const npmStatsStartDate = new Date('2015-01-10'); + if (startDate < npmStatsStartDate) { + startDate.setTime(npmStatsStartDate.getTime()); + } + + // For calculating weekly trend (previous week) + const prevWeekEnd = new Date(); + prevWeekEnd.setDate(prevWeekEnd.getDate() - 7); + const prevWeekStart = new Date(); + prevWeekStart.setDate(prevWeekStart.getDate() - 14); + + // For calculating monthly trend (previous month) + const prevMonthEnd = new Date(); + prevMonthEnd.setDate(prevMonthEnd.getDate() - 30); + const prevMonthStart = new Date(); + prevMonthStart.setDate(prevMonthStart.getDate() - 60); + + const prevWeekRange = `${formatDate(prevWeekStart)}:${formatDate(prevWeekEnd)}`; + const prevMonthRange = `${formatDate(prevMonthStart)}:${formatDate(prevMonthEnd)}`; + + // Create date chunks for all-time data (npm API limit: 18 months per request) + const dateChunks = createDateChunks(startDate, endDate); + + const fetchPromises = [ + fetch(`${NPM_DOWNLOADS_API}/point/last-week/${PACKAGE_NAME}`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }), + fetch(`${NPM_DOWNLOADS_API}/point/last-month/${PACKAGE_NAME}`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }), + fetch(`https://api.npmjs.org/versions/${PACKAGE_NAME}/last-week`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }), + fetch(`${NPM_DOWNLOADS_API}/range/${prevWeekRange}/${PACKAGE_NAME}`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }), + fetch(`${NPM_DOWNLOADS_API}/range/${prevMonthRange}/${PACKAGE_NAME}`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }), + ...dateChunks.map(chunk => + fetch(`${NPM_DOWNLOADS_API}/range/${chunk.start}:${chunk.end}/${PACKAGE_NAME}`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }) + ) + ]; + + const responses = await Promise.all(fetchPromises); + + // Check if all requests succeeded + if (responses.some(res => !res.ok)) { + throw new Error('Failed to fetch dashboard data'); + } + + // Parse responses + const [weeklyRes, monthlyRes, versionsRes, prevWeekRes, prevMonthRes, ...chunkResponses] = responses; + + const [weekly, monthly, versions, prevWeek, prevMonth]: [ + DownloadPoint, + DownloadPoint, + VersionDownloads, + DownloadRange, + DownloadRange + ] = await Promise.all([ + weeklyRes.json(), + monthlyRes.json(), + versionsRes.json(), + prevWeekRes.json(), + prevMonthRes.json(), + ]); + + // Parse and combine all chunk data + const chunkDataArrays: DownloadRange[] = await Promise.all( + chunkResponses.map(res => res.json()) + ); + + // Merge all downloads chronologically + const allDownloads = chunkDataArrays.flatMap(chunk => chunk.downloads); + + const range: DownloadRange = { + start: formatDate(startDate), + end: formatDate(endDate), + package: PACKAGE_NAME, + downloads: allDownloads + }; + + // Process metadata the same way as the API route + const latestVersion = rawMetadata['dist-tags']?.latest || ''; + const latestVersionData = latestVersion ? rawMetadata.versions[latestVersion] : null; + const versionsCount = Object.keys(rawMetadata.versions).length; + const lastPublished = rawMetadata.time?.[latestVersion] || ''; + + const metadata = { + name: rawMetadata.name, + description: rawMetadata.description, + latestVersion, + versionsCount, + lastPublished, + license: latestVersionData?.license || rawMetadata.license, + author: rawMetadata.author, + maintainers: rawMetadata.maintainers, + keywords: rawMetadata.keywords, + repository: rawMetadata.repository, + homepage: rawMetadata.homepage, + bugs: rawMetadata.bugs, + unpackedSize: latestVersionData?.dist?.unpackedSize, + fileCount: latestVersionData?.dist?.fileCount, + dependencies: latestVersionData?.dependencies, + devDependencies: latestVersionData?.devDependencies, + peerDependencies: latestVersionData?.peerDependencies, + }; + + // Calculate trends + const prevWeekTotal = prevWeek.downloads.reduce((sum, day) => sum + day.downloads, 0); + const prevMonthTotal = prevMonth.downloads.reduce((sum, day) => sum + day.downloads, 0); + + const weeklyTrend = prevWeekTotal > 0 + ? Math.round(((weekly.downloads - prevWeekTotal) / prevWeekTotal) * 100) + : 0; + + const monthlyTrend = prevMonthTotal > 0 + ? Math.round(((monthly.downloads - prevMonthTotal) / prevMonthTotal) * 100) + : 0; + + // Fetch GitHub stars, dependents, and repositories data in parallel + let githubStars: number | null = null; + + const additionalFetches: Promise<{ type: string; data: number | null }>[] = []; + + // Add GitHub stars fetch if repository is available + if (metadata.repository) { + try { + const repoUrl = typeof metadata.repository === 'string' + ? metadata.repository + : metadata.repository.url; + const match = repoUrl.match(/github\.com[/:]([^/]+)\/([^/.]+)/); + if (match) { + const [, owner, repo] = match; + const cleanRepo = repo.replace(/\.git$/, ''); + additionalFetches.push( + fetch(`https://api.github.com/repos/${owner}/${cleanRepo}`, { + next: { revalidate: 3600 }, + }).then(async (res) => { + if (res.ok) { + const data = await res.json(); + return { type: 'github', data: data.stargazers_count }; + } + return { type: 'github', data: null }; + }).catch(() => ({ type: 'github', data: null })) + ); + } + } catch (error) { + console.error('Error preparing GitHub fetch:', error); + } + } + + // Execute additional fetches in parallel + const additionalResults = await Promise.all(additionalFetches); + additionalResults.forEach(result => { + if (result.type === 'github') { + githubStars = result.data; + } + }); + + return { + weekly, + monthly, + range, + metadata, + versions, + trends: { + weekly: weeklyTrend, + monthly: monthlyTrend, + }, + githubStars, + }; + } catch (error) { + console.error('Error fetching dashboard data:', error); + throw error; + } +} + +function MetricCardSkeleton() { + return ( + + + + + + + + +
+ + +
+
+
+ ); +} + +async function VersionBadge() { + const res = await fetch('https://registry.npmjs.org/xmcp', { + next: { revalidate: 3600 }, + cache: 'force-cache' + }); + const data = await res.json(); + const latestVersion = data['dist-tags']?.latest || ''; + + return ( + + v{latestVersion} + + ); +} + +function DashboardSkeleton() { + return ( +
+ {/* Metrics Cards */} +
+ {[...Array(4)].map((_, i) => ( + + ))} +
+ + {/* Download Trend Chart */} + + +
+ + +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+
+ + + +
+ + {/* Version Distribution - 2 column grid */} +
+ {/* Chart Card */} + + + + + + + + + {/* Table Card */} + + + + + + + + + + + + + + + {[...Array(5)].map((_, i) => ( + + + + + + ))} + +
+
+
+ + {/* Package Info */} + + + {/* Stats Grid */} +
+ {[...Array(4)].map((_, i) => ( +
+ +
+ + +
+
+ ))} + {/* Maintainers section */} +
+ +
+ +
+ {[...Array(2)].map((_, i) => ( +
+ + +
+ ))} +
+
+
+
+ + {/* Links */} + {/*
+ +
+ + + +
+
*/} +
+
+
+ ); +} + +async function DashboardContent() { + const data = await getDashboardData(); + + const totalRangeDownloads = data.range.downloads.reduce( + (sum, day) => sum + day.downloads, + 0 + ); + + return ( +
+ {/* Metrics Cards */} + + + {/* Download Trend Chart */} + + + {/* Version Distribution */} + + + {/* Package Dependents */} + {/* {data.dependents && (data.dependents as LibrariesIODependentsResponse).dependents_count > 0 && ( + + )} */} + + {/* Package Info Header */} + +
+ ); +} + +export default function Home() { + return ( +
+
+ {/* Static header - renders immediately */} +
+
+ + + + + + + + + }> + + +
+

+ Real-time analytics and metrics for the xmcp npm package +

+
+ + {/* Dashboard content */} + }> + + +
+
+ ); +} diff --git a/apps/website/basehub-types.d.ts b/apps/website/basehub-types.d.ts index 416cce5b..68e2348a 100644 --- a/apps/website/basehub-types.d.ts +++ b/apps/website/basehub-types.d.ts @@ -58,62 +58,10 @@ export interface Scalars { Int: number, JSON: any, String: string, - bshb_event__1212762555: `bshb_event__1212762555:${string}`, - schema_bshb_event__1212762555: {repositoryUrl?: string;connection: string;logo?: File;projectName: string;tagline: string;contactEmail: string;}, } export type AnalyticsKeyScope = 'query' | 'send' -export interface ArticleComponent { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - /** Array of search highlight information with field names and HTML markup */ - _highlight: (SearchHighlight[] | null) - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - body: Body - ogImage: BlockOgImage - __typename: 'ArticleComponent' -} - -export type ArticleComponentOrderByEnum = '_sys_createdAt__ASC' | '_sys_createdAt__DESC' | '_sys_hash__ASC' | '_sys_hash__DESC' | '_sys_id__ASC' | '_sys_id__DESC' | '_sys_lastModifiedAt__ASC' | '_sys_lastModifiedAt__DESC' | '_sys_slug__ASC' | '_sys_slug__DESC' | '_sys_title__ASC' | '_sys_title__DESC' | 'body__ASC' | 'body__DESC' | 'ogImage__ASC' | 'ogImage__DESC' - -export interface Articles { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item: (ArticleComponent | null) - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items: ArticleComponent[] - __typename: 'Articles' -} - -export interface Assets { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - glLogoMatcap: BlockImage - __typename: 'Assets' -} - export interface BaseRichTextJson { blocks: Scalars['String'] content: Scalars['BSHBRichTextContentSchema'] @@ -151,7 +99,17 @@ export interface BlockColor { __typename: 'BlockColor' } -export type BlockDocument = (ArticleComponent | Articles | Assets | Collection | Documentation | McpTemplateComponent | Mcps | Showcase | SidebarTree | SidebarTreeComponent | _AgentStart | articleComponent_AsList | mcpTemplateComponent_AsList | sidebarTreeComponent_AsList) & { __isUnion?: true } +export interface BlockDocument { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + __typename: string +} export interface BlockDocumentSys { apiNamePath: Scalars['String'] @@ -210,7 +168,20 @@ export interface BlockImage { __typename: 'BlockImage' } -export type BlockList = (Articles | Collection | Mcps | SidebarTree | articleComponent_AsList | mcpTemplateComponent_AsList | sidebarTreeComponent_AsList) & { __isUnion?: true } +export interface BlockList { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + __typename: string +} export interface BlockOgImage { height: Scalars['Int'] @@ -221,7 +192,14 @@ export interface BlockOgImage { /** Rich text block */ -export type BlockRichText = (Body) & { __isUnion?: true } +export interface BlockRichText { + html: Scalars['String'] + json: RichTextJson + markdown: Scalars['String'] + plainText: Scalars['String'] + readingTime: Scalars['Int'] + __typename: string +} export interface BlockVideo { aspectRatio: Scalars['String'] @@ -237,54 +215,6 @@ export interface BlockVideo { __typename: 'BlockVideo' } -export interface Body { - html: Scalars['String'] - json: BodyRichText - markdown: Scalars['String'] - plainText: Scalars['String'] - readingTime: Scalars['Int'] - __typename: 'Body' -} - -export interface BodyRichText { - content: Scalars['BSHBRichTextContentSchema'] - toc: Scalars['BSHBRichTextTOCSchema'] - __typename: 'BodyRichText' -} - -export interface Collection { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item: (SidebarTreeComponent | null) - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items: SidebarTreeComponent[] - __typename: 'Collection' -} - -export interface Documentation { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - articles: Articles - sidebarTree: SidebarTree - __typename: 'Documentation' -} - export interface GetUploadSignedURL { signedURL: Scalars['String'] uploadURL: Scalars['String'] @@ -299,47 +229,6 @@ export interface ListMeta { __typename: 'ListMeta' } -export interface McpTemplateComponent { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - /** Array of search highlight information with field names and HTML markup */ - _highlight: (SearchHighlight[] | null) - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - connection: Scalars['String'] - logo: BlockImage - name: Scalars['String'] - repositoryUrl: (Scalars['String'] | null) - tag: (Scalars['String'] | null) - tagline: Scalars['String'] - __typename: 'McpTemplateComponent' -} - -export type McpTemplateComponentOrderByEnum = '_sys_createdAt__ASC' | '_sys_createdAt__DESC' | '_sys_hash__ASC' | '_sys_hash__DESC' | '_sys_id__ASC' | '_sys_id__DESC' | '_sys_lastModifiedAt__ASC' | '_sys_lastModifiedAt__DESC' | '_sys_slug__ASC' | '_sys_slug__DESC' | '_sys_title__ASC' | '_sys_title__DESC' | 'connection__ASC' | 'connection__DESC' | 'logo__ASC' | 'logo__DESC' | 'name__ASC' | 'name__DESC' | 'repositoryUrl__ASC' | 'repositoryUrl__DESC' | 'tag__ASC' | 'tag__DESC' | 'tagline__ASC' | 'tagline__DESC' - -export interface Mcps { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item: (McpTemplateComponent | null) - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items: McpTemplateComponent[] - __typename: 'Mcps' -} - export type MediaBlock = (BlockAudio | BlockFile | BlockImage | BlockVideo) & { __isUnion?: true } export type MediaBlockUnion = (BlockAudio | BlockFile | BlockImage | BlockVideo) & { __isUnion?: true } @@ -378,19 +267,11 @@ export interface Mutation { } export interface Query { - _agent: (_AgentStart | null) - /** Query across the custom AI agents in the repository. */ - _agents: _agents - /** Query across all of the instances of a component. Pass in filters and sorts if you want, and get each instance via the `items` key. */ - _componentInstances: _components /** The diff between the current branch and the head commit. */ _diff: Scalars['JSON'] /** The structure of the repository. Used by START. */ _structure: Scalars['JSON'] _sys: RepoSys - assets: Assets - documentation: Documentation - showcase: Showcase __typename: 'Query' } @@ -406,7 +287,7 @@ export interface RepoSys { __typename: 'RepoSys' } -export type RichTextJson = (BaseRichTextJson | BodyRichText) & { __isUnion?: true } +export type RichTextJson = (BaseRichTextJson) & { __isUnion?: true } export interface SearchHighlight { /** The field/path that was matched (e.g., "title", "body.content") */ @@ -416,66 +297,6 @@ export interface SearchHighlight { __typename: 'SearchHighlight' } -export interface Showcase { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - mcps: Mcps - submissions: Submissions - __typename: 'Showcase' -} - -export interface SidebarTree { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item: (SidebarTreeComponent | null) - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items: SidebarTreeComponent[] - __typename: 'SidebarTree' -} - -export interface SidebarTreeComponent { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - /** Array of search highlight information with field names and HTML markup */ - _highlight: (SearchHighlight[] | null) - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - collection: Collection - target: ArticleComponent - __typename: 'SidebarTreeComponent' -} - -export type SidebarTreeComponentOrderByEnum = '_sys_createdAt__ASC' | '_sys_createdAt__DESC' | '_sys_hash__ASC' | '_sys_hash__DESC' | '_sys_id__ASC' | '_sys_id__DESC' | '_sys_lastModifiedAt__ASC' | '_sys_lastModifiedAt__DESC' | '_sys_slug__ASC' | '_sys_slug__DESC' | '_sys_title__ASC' | '_sys_title__DESC' | 'collection__ASC' | 'collection__DESC' | 'target__ASC' | 'target__DESC' | 'untitled__ASC' | 'untitled__DESC' - -export interface Submissions { - /** The `adminKey` gives clients the ability to query, delete and update this block's data. **It's not meant to be exposed to the public.** */ - adminKey: Scalars['bshb_event__1212762555'] - /** The `ingestKey` gives clients the ability to send new events to this block. Generally, it's safe to expose it to the public. */ - ingestKey: Scalars['bshb_event__1212762555'] - schema: Scalars['BSHBEventSchema'] - __typename: 'Submissions' -} - export interface TransactionStatus { /** Duration in milliseconds. */ duration: (Scalars['Int'] | null) @@ -498,35 +319,6 @@ export interface Variant { __typename: 'Variant' } -export interface _AgentStart { - _agentKey: Scalars['String'] - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - accent: Scalars['String'] - avatar: Scalars['String'] - chatUrl: Scalars['String'] - commit: Scalars['Boolean'] - description: Scalars['String'] - edit: Scalars['Boolean'] - embedUrl: Scalars['String'] - getUserInfo: Scalars['Boolean'] - grayscale: Scalars['String'] - manageBranches: Scalars['Boolean'] - mcpUrl: Scalars['String'] - model: Scalars['String'] - openRouterKey: (Scalars['String'] | null) - searchTheWeb: Scalars['Boolean'] - slackInstallUrl: Scalars['String'] - systemPrompt: Scalars['String'] - __typename: '_AgentStart' -} - export interface _BranchInfo { archivedAt: (Scalars['String'] | null) archivedBy: (Scalars['String'] | null) @@ -592,152 +384,6 @@ export type _ResolveTargetsWithEnum = 'id' | 'objectName' export type _StructureFormatEnum = 'json' | 'xml' -export interface _agents { - start: _AgentStart - __typename: '_agents' -} - -export interface _components { - article: articleComponent_AsList - mcpTemplate: mcpTemplateComponent_AsList - sidebarTree: sidebarTreeComponent_AsList - __typename: '_components' -} - -export interface articleComponent_AsList { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item: (ArticleComponent | null) - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items: ArticleComponent[] - __typename: 'articleComponent_AsList' -} - -export interface mcpTemplateComponent_AsList { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item: (McpTemplateComponent | null) - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items: McpTemplateComponent[] - __typename: 'mcpTemplateComponent_AsList' -} - -export interface sidebarTreeComponent_AsList { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item: (SidebarTreeComponent | null) - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items: SidebarTreeComponent[] - __typename: 'sidebarTreeComponent_AsList' -} - -export interface ArticleComponentGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - /** Array of search highlight information with field names and HTML markup */ - _highlight?: SearchHighlightGenqlSelection - _id?: boolean | number - _idPath?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - body?: BodyGenqlSelection - ogImage?: BlockOgImageGenqlSelection - __typename?: boolean | number - __fragmentOn?: "ArticleComponent" -} - -export interface ArticleComponentFilterInput {AND?: (ArticleComponentFilterInput | null),OR?: (ArticleComponentFilterInput | null),_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null)} - -export interface ArticleComponentSearchInput { -/** Searchable fields for query */ -by?: (Scalars['String'][] | null), -/** Search query */ -q?: (Scalars['String'] | null)} - -export interface ArticlesGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item?: ArticleComponentGenqlSelection - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items?: ArticleComponentGenqlSelection - __typename?: boolean | number - __fragmentOn?: "Articles" -} - -export interface AssetsGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - glLogoMatcap?: BlockImageGenqlSelection - __typename?: boolean | number - __fragmentOn?: "Assets" -} - export interface BaseRichTextJsonGenqlSelection{ blocks?: boolean | number content?: boolean | number @@ -796,20 +442,6 @@ export interface BlockDocumentGenqlSelection{ _slugPath?: boolean | number _sys?: BlockDocumentSysGenqlSelection _title?: boolean | number - on_ArticleComponent?: ArticleComponentGenqlSelection - on_Articles?: ArticlesGenqlSelection - on_Assets?: AssetsGenqlSelection - on_Collection?: CollectionGenqlSelection - on_Documentation?: DocumentationGenqlSelection - on_McpTemplateComponent?: McpTemplateComponentGenqlSelection - on_Mcps?: McpsGenqlSelection - on_Showcase?: ShowcaseGenqlSelection - on_SidebarTree?: SidebarTreeGenqlSelection - on_SidebarTreeComponent?: SidebarTreeComponentGenqlSelection - on__AgentStart?: _AgentStartGenqlSelection - on_articleComponent_AsList?: articleComponent_AsListGenqlSelection - on_mcpTemplateComponent_AsList?: mcpTemplateComponent_AsListGenqlSelection - on_sidebarTreeComponent_AsList?: sidebarTreeComponent_AsListGenqlSelection __typename?: boolean | number __fragmentOn?: "BlockDocument" } @@ -867,136 +499,14 @@ export interface BlockImageGenqlSelection{ * * BaseHub uses Cloudflare for image resizing. Check out [all available options in their docs](https://developers.cloudflare.com/images/transform-images/transform-via-workers/#fetch-options). * - */ - url?: { __args: {anim?: (Scalars['String'] | null), background?: (Scalars['String'] | null), blur?: (Scalars['Int'] | null), border?: (Scalars['String'] | null), brightness?: (Scalars['Int'] | null), compression?: (Scalars['String'] | null), contrast?: (Scalars['Int'] | null), dpr?: (Scalars['Int'] | null), fit?: (Scalars['String'] | null), format?: (Scalars['String'] | null), gamma?: (Scalars['String'] | null), gravity?: (Scalars['String'] | null), height?: (Scalars['Int'] | null), metadata?: (Scalars['String'] | null), quality?: (Scalars['Int'] | null), rotate?: (Scalars['String'] | null), sharpen?: (Scalars['String'] | null), trim?: (Scalars['String'] | null), width?: (Scalars['Int'] | null)} } | boolean | number - width?: boolean | number - __typename?: boolean | number - __fragmentOn?: "BlockImage" -} - -export interface BlockListGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - on_Articles?: ArticlesGenqlSelection - on_Collection?: CollectionGenqlSelection - on_Mcps?: McpsGenqlSelection - on_SidebarTree?: SidebarTreeGenqlSelection - on_articleComponent_AsList?: articleComponent_AsListGenqlSelection - on_mcpTemplateComponent_AsList?: mcpTemplateComponent_AsListGenqlSelection - on_sidebarTreeComponent_AsList?: sidebarTreeComponent_AsListGenqlSelection - __typename?: boolean | number - __fragmentOn?: "BlockList" -} - -export interface BlockOgImageGenqlSelection{ - height?: boolean | number - url?: boolean | number - width?: boolean | number - __typename?: boolean | number - __fragmentOn?: "BlockOgImage" -} - - -/** Rich text block */ -export interface BlockRichTextGenqlSelection{ - html?: { __args: { - /** It automatically generates a unique id for each heading present in the HTML. Enabled by default. */ - slugs?: (Scalars['Boolean'] | null), - /** Inserts a table of contents at the beginning of the HTML. */ - toc?: (Scalars['Boolean'] | null)} } | boolean | number - json?: RichTextJsonGenqlSelection - markdown?: boolean | number - plainText?: boolean | number - readingTime?: { __args: { - /** Words per minute, defaults to average 183wpm */ - wpm?: (Scalars['Int'] | null)} } | boolean | number - on_Body?: BodyGenqlSelection - __typename?: boolean | number - __fragmentOn?: "BlockRichText" -} - -export interface BlockVideoGenqlSelection{ - aspectRatio?: boolean | number - /** The duration of the video in seconds. If the duration is not available, it will be estimated based on the file size. */ - duration?: boolean | number - fileName?: boolean | number - fileSize?: boolean | number - height?: boolean | number - lastModified?: boolean | number - mimeType?: boolean | number - url?: boolean | number - width?: boolean | number - __typename?: boolean | number - __fragmentOn?: "BlockVideo" -} - -export interface BodyGenqlSelection{ - html?: { __args: { - /** It automatically generates a unique id for each heading present in the HTML. Enabled by default. */ - slugs?: (Scalars['Boolean'] | null), - /** Inserts a table of contents at the beginning of the HTML. */ - toc?: (Scalars['Boolean'] | null)} } | boolean | number - json?: BodyRichTextGenqlSelection - markdown?: boolean | number - plainText?: boolean | number - readingTime?: { __args: { - /** Words per minute, defaults to average 183wpm */ - wpm?: (Scalars['Int'] | null)} } | boolean | number - __typename?: boolean | number - __fragmentOn?: "Body" -} - -export interface BodyRichTextGenqlSelection{ - content?: boolean | number - toc?: boolean | number - __typename?: boolean | number - __fragmentOn?: "BodyRichText" -} - -export interface CollectionGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item?: SidebarTreeComponentGenqlSelection - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items?: SidebarTreeComponentGenqlSelection + */ + url?: { __args: {anim?: (Scalars['String'] | null), background?: (Scalars['String'] | null), blur?: (Scalars['Int'] | null), border?: (Scalars['String'] | null), brightness?: (Scalars['Int'] | null), compression?: (Scalars['String'] | null), contrast?: (Scalars['Int'] | null), dpr?: (Scalars['Int'] | null), fit?: (Scalars['String'] | null), format?: (Scalars['String'] | null), gamma?: (Scalars['String'] | null), gravity?: (Scalars['String'] | null), height?: (Scalars['Int'] | null), metadata?: (Scalars['String'] | null), quality?: (Scalars['Int'] | null), rotate?: (Scalars['String'] | null), sharpen?: (Scalars['String'] | null), trim?: (Scalars['String'] | null), width?: (Scalars['Int'] | null)} } | boolean | number + width?: boolean | number __typename?: boolean | number - __fragmentOn?: "Collection" + __fragmentOn?: "BlockImage" } -export interface DateFilter {eq?: (Scalars['DateTime'] | null),isAfter?: (Scalars['DateTime'] | null),isBefore?: (Scalars['DateTime'] | null),isNull?: (Scalars['Boolean'] | null),neq?: (Scalars['DateTime'] | null),onOrAfter?: (Scalars['DateTime'] | null),onOrBefore?: (Scalars['DateTime'] | null)} - -export interface DocumentationGenqlSelection{ +export interface BlockListGenqlSelection{ _analyticsKey?: { __args: { /** * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. @@ -1007,36 +517,60 @@ export interface DocumentationGenqlSelection{ _dashboardUrl?: boolean | number _id?: boolean | number _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number _slug?: boolean | number _slugPath?: boolean | number _sys?: BlockDocumentSysGenqlSelection _title?: boolean | number - articles?: (ArticlesGenqlSelection & { __args?: { - /** Filter by a field. */ - filter?: (ArticleComponentFilterInput | null), - /** Limit the number of items returned. Defaults to 500. */ - first?: (Scalars['Int'] | null), - /** Order by a field. */ - orderBy?: (ArticleComponentOrderByEnum | null), - /** Search configuration */ - search?: (ArticleComponentSearchInput | null), - /** Skip the first n items. */ - skip?: (Scalars['Int'] | null)} }) - sidebarTree?: (SidebarTreeGenqlSelection & { __args?: { - /** Filter by a field. */ - filter?: (SidebarTreeComponentFilterInput | null), - /** Limit the number of items returned. Defaults to 500. */ - first?: (Scalars['Int'] | null), - /** Order by a field. */ - orderBy?: (SidebarTreeComponentOrderByEnum | null), - /** Search configuration */ - search?: (SidebarTreeComponentSearchInput | null), - /** Skip the first n items. */ - skip?: (Scalars['Int'] | null)} }) __typename?: boolean | number - __fragmentOn?: "Documentation" + __fragmentOn?: "BlockList" +} + +export interface BlockOgImageGenqlSelection{ + height?: boolean | number + url?: boolean | number + width?: boolean | number + __typename?: boolean | number + __fragmentOn?: "BlockOgImage" +} + + +/** Rich text block */ +export interface BlockRichTextGenqlSelection{ + html?: { __args: { + /** It automatically generates a unique id for each heading present in the HTML. Enabled by default. */ + slugs?: (Scalars['Boolean'] | null), + /** Inserts a table of contents at the beginning of the HTML. */ + toc?: (Scalars['Boolean'] | null)} } | boolean | number + json?: RichTextJsonGenqlSelection + markdown?: boolean | number + plainText?: boolean | number + readingTime?: { __args: { + /** Words per minute, defaults to average 183wpm */ + wpm?: (Scalars['Int'] | null)} } | boolean | number + __typename?: boolean | number + __fragmentOn?: "BlockRichText" +} + +export interface BlockVideoGenqlSelection{ + aspectRatio?: boolean | number + /** The duration of the video in seconds. If the duration is not available, it will be estimated based on the file size. */ + duration?: boolean | number + fileName?: boolean | number + fileSize?: boolean | number + height?: boolean | number + lastModified?: boolean | number + mimeType?: boolean | number + url?: boolean | number + width?: boolean | number + __typename?: boolean | number + __fragmentOn?: "BlockVideo" } +export interface DateFilter {eq?: (Scalars['DateTime'] | null),isAfter?: (Scalars['DateTime'] | null),isBefore?: (Scalars['DateTime'] | null),isNull?: (Scalars['Boolean'] | null),neq?: (Scalars['DateTime'] | null),onOrAfter?: (Scalars['DateTime'] | null),onOrBefore?: (Scalars['DateTime'] | null)} + export interface GetUploadSignedURLGenqlSelection{ signedURL?: boolean | number uploadURL?: boolean | number @@ -1055,67 +589,6 @@ export interface ListMetaGenqlSelection{ __fragmentOn?: "ListMeta" } -export interface McpTemplateComponentGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - /** Array of search highlight information with field names and HTML markup */ - _highlight?: SearchHighlightGenqlSelection - _id?: boolean | number - _idPath?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - connection?: boolean | number - logo?: BlockImageGenqlSelection - name?: boolean | number - repositoryUrl?: boolean | number - tag?: boolean | number - tagline?: boolean | number - __typename?: boolean | number - __fragmentOn?: "McpTemplateComponent" -} - -export interface McpTemplateComponentFilterInput {AND?: (McpTemplateComponentFilterInput | null),OR?: (McpTemplateComponentFilterInput | null),_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null),connection?: (StringFilter | null),name?: (StringFilter | null),repositoryUrl?: (StringFilter | null),tag?: (StringFilter | null),tagline?: (StringFilter | null)} - -export interface McpTemplateComponentSearchInput { -/** Searchable fields for query */ -by?: (Scalars['String'][] | null), -/** Search query */ -q?: (Scalars['String'] | null)} - -export interface McpsGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item?: McpTemplateComponentGenqlSelection - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items?: McpTemplateComponentGenqlSelection - __typename?: boolean | number - __fragmentOn?: "Mcps" -} - export interface MediaBlockGenqlSelection{ fileName?: boolean | number fileSize?: boolean | number @@ -1201,13 +674,6 @@ export interface MutationGenqlSelection{ export interface NumberFilter {eq?: (Scalars['Float'] | null),gt?: (Scalars['Float'] | null),gte?: (Scalars['Float'] | null),isNull?: (Scalars['Boolean'] | null),lt?: (Scalars['Float'] | null),lte?: (Scalars['Float'] | null),neq?: (Scalars['Float'] | null)} export interface QueryGenqlSelection{ - _agent?: (_AgentStartGenqlSelection & { __args: { - /** The ID of the agent. */ - id: Scalars['String']} }) - /** Query across the custom AI agents in the repository. */ - _agents?: _agentsGenqlSelection - /** Query across all of the instances of a component. Pass in filters and sorts if you want, and get each instance via the `items` key. */ - _componentInstances?: _componentsGenqlSelection /** The diff between the current branch and the head commit. */ _diff?: { __args: { /** Simplified diff returns only the items array showing statuses. */ @@ -1227,9 +693,6 @@ export interface QueryGenqlSelection{ /** Whether to include type options in the structure. */ withTypeOptions?: (Scalars['Boolean'] | null)} } | boolean | number _sys?: RepoSysGenqlSelection - assets?: AssetsGenqlSelection - documentation?: DocumentationGenqlSelection - showcase?: ShowcaseGenqlSelection __typename?: boolean | number __fragmentOn?: "Query" } @@ -1251,7 +714,6 @@ export interface RichTextJsonGenqlSelection{ content?: boolean | number toc?: boolean | number on_BaseRichTextJson?: BaseRichTextJsonGenqlSelection - on_BodyRichText?: BodyRichTextGenqlSelection __typename?: boolean | number __fragmentOn?: "RichTextJson" } @@ -1267,120 +729,10 @@ export interface SearchHighlightGenqlSelection{ export interface SelectFilter {excludes?: (Scalars['String'] | null),excludesAll?: (Scalars['String'][] | null),includes?: (Scalars['String'] | null),includesAll?: (Scalars['String'][] | null),includesAny?: (Scalars['String'][] | null),isEmpty?: (Scalars['Boolean'] | null)} -export interface ShowcaseGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - mcps?: (McpsGenqlSelection & { __args?: { - /** Filter by a field. */ - filter?: (McpTemplateComponentFilterInput | null), - /** Limit the number of items returned. Defaults to 500. */ - first?: (Scalars['Int'] | null), - /** Order by a field. */ - orderBy?: (McpTemplateComponentOrderByEnum | null), - /** Search configuration */ - search?: (McpTemplateComponentSearchInput | null), - /** Skip the first n items. */ - skip?: (Scalars['Int'] | null)} }) - submissions?: SubmissionsGenqlSelection - __typename?: boolean | number - __fragmentOn?: "Showcase" -} - -export interface SidebarTreeGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item?: SidebarTreeComponentGenqlSelection - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items?: SidebarTreeComponentGenqlSelection - __typename?: boolean | number - __fragmentOn?: "SidebarTree" -} - -export interface SidebarTreeComponentGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - /** Array of search highlight information with field names and HTML markup */ - _highlight?: SearchHighlightGenqlSelection - _id?: boolean | number - _idPath?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - collection?: (CollectionGenqlSelection & { __args?: { - /** Filter by a field. */ - filter?: (SidebarTreeComponentFilterInput | null), - /** Limit the number of items returned. Defaults to 500. */ - first?: (Scalars['Int'] | null), - /** Order by a field. */ - orderBy?: (SidebarTreeComponentOrderByEnum | null), - /** Search configuration */ - search?: (SidebarTreeComponentSearchInput | null), - /** Skip the first n items. */ - skip?: (Scalars['Int'] | null)} }) - target?: ArticleComponentGenqlSelection - __typename?: boolean | number - __fragmentOn?: "SidebarTreeComponent" -} - -export interface SidebarTreeComponentFilterInput {AND?: (SidebarTreeComponentFilterInput | null),OR?: (SidebarTreeComponentFilterInput | null),_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null),collection?: (ListFilter | null),target?: (SidebarTreeComponentFilterInput__target_0___article | null)} - -export interface SidebarTreeComponentFilterInput__target_0___article {_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null)} - -export interface SidebarTreeComponentSearchInput { -/** Searchable fields for query */ -by?: (Scalars['String'][] | null), -/** Search query */ -q?: (Scalars['String'] | null)} - export interface StringFilter {contains?: (Scalars['String'] | null),endsWith?: (Scalars['String'] | null),eq?: (Scalars['String'] | null),in?: (Scalars['String'][] | null),isNull?: (Scalars['Boolean'] | null),matches?: (StringMatchesFilter | null),notEq?: (Scalars['String'] | null),notIn?: (Scalars['String'][] | null),startsWith?: (Scalars['String'] | null)} export interface StringMatchesFilter {caseSensitive?: (Scalars['Boolean'] | null),pattern: Scalars['String']} -export interface SubmissionsGenqlSelection{ - /** The `adminKey` gives clients the ability to query, delete and update this block's data. **It's not meant to be exposed to the public.** */ - adminKey?: boolean | number - /** The `ingestKey` gives clients the ability to send new events to this block. Generally, it's safe to expose it to the public. */ - ingestKey?: boolean | number - schema?: boolean | number - __typename?: boolean | number - __fragmentOn?: "Submissions" -} - export interface TargetBlock {focus?: (Scalars['Boolean'] | null),id: Scalars['String'],label: Scalars['String']} export interface TransactionStatusGenqlSelection{ @@ -1405,42 +757,6 @@ export interface VariantGenqlSelection{ __fragmentOn?: "Variant" } -export interface _AgentStartGenqlSelection{ - _agentKey?: boolean | number - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - accent?: boolean | number - avatar?: boolean | number - chatUrl?: boolean | number - commit?: boolean | number - description?: boolean | number - edit?: boolean | number - embedUrl?: boolean | number - getUserInfo?: boolean | number - grayscale?: boolean | number - manageBranches?: boolean | number - mcpUrl?: boolean | number - model?: boolean | number - openRouterKey?: boolean | number - searchTheWeb?: boolean | number - slackInstallUrl?: boolean | number - systemPrompt?: boolean | number - __typename?: boolean | number - __fragmentOn?: "_AgentStart" -} - export interface _BranchInfoGenqlSelection{ archivedAt?: boolean | number archivedBy?: boolean | number @@ -1507,141 +823,7 @@ export interface _PlaygroundInfoGenqlSelection{ __fragmentOn?: "_PlaygroundInfo" } -export interface _agentsGenqlSelection{ - start?: _AgentStartGenqlSelection - __typename?: boolean | number - __fragmentOn?: "_agents" -} - -export interface _componentsGenqlSelection{ - article?: (articleComponent_AsListGenqlSelection & { __args?: { - /** Filter by a field. */ - filter?: (ArticleComponentFilterInput | null), - /** Limit the number of items returned. Defaults to 500. */ - first?: (Scalars['Int'] | null), - /** Order by a field. */ - orderBy?: (ArticleComponentOrderByEnum | null), - /** Search configuration */ - search?: (ArticleComponentSearchInput | null), - /** Skip the first n items. */ - skip?: (Scalars['Int'] | null)} }) - mcpTemplate?: (mcpTemplateComponent_AsListGenqlSelection & { __args?: { - /** Filter by a field. */ - filter?: (McpTemplateComponentFilterInput | null), - /** Limit the number of items returned. Defaults to 500. */ - first?: (Scalars['Int'] | null), - /** Order by a field. */ - orderBy?: (McpTemplateComponentOrderByEnum | null), - /** Search configuration */ - search?: (McpTemplateComponentSearchInput | null), - /** Skip the first n items. */ - skip?: (Scalars['Int'] | null)} }) - sidebarTree?: (sidebarTreeComponent_AsListGenqlSelection & { __args?: { - /** Filter by a field. */ - filter?: (SidebarTreeComponentFilterInput | null), - /** Limit the number of items returned. Defaults to 500. */ - first?: (Scalars['Int'] | null), - /** Order by a field. */ - orderBy?: (SidebarTreeComponentOrderByEnum | null), - /** Search configuration */ - search?: (SidebarTreeComponentSearchInput | null), - /** Skip the first n items. */ - skip?: (Scalars['Int'] | null)} }) - __typename?: boolean | number - __fragmentOn?: "_components" -} - -export interface articleComponent_AsListGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item?: ArticleComponentGenqlSelection - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items?: ArticleComponentGenqlSelection - __typename?: boolean | number - __fragmentOn?: "articleComponent_AsList" -} - -export interface mcpTemplateComponent_AsListGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item?: McpTemplateComponentGenqlSelection - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items?: McpTemplateComponentGenqlSelection - __typename?: boolean | number - __fragmentOn?: "mcpTemplateComponent_AsList" -} - -export interface sidebarTreeComponent_AsListGenqlSelection{ - _analyticsKey?: { __args: { - /** - * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. - * - * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. - */ - scope?: (AnalyticsKeyScope | null)} } | boolean | number - _dashboardUrl?: boolean | number - _id?: boolean | number - _idPath?: boolean | number - _meta?: ListMetaGenqlSelection - /** The key used to search from the frontend. */ - _searchKey?: boolean | number - _slug?: boolean | number - _slugPath?: boolean | number - _sys?: BlockDocumentSysGenqlSelection - _title?: boolean | number - /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ - item?: SidebarTreeComponentGenqlSelection - /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ - items?: SidebarTreeComponentGenqlSelection - __typename?: boolean | number - __fragmentOn?: "sidebarTreeComponent_AsList" -} - export interface FragmentsMap { - ArticleComponent: { - root: ArticleComponent, - selection: ArticleComponentGenqlSelection, -} - Articles: { - root: Articles, - selection: ArticlesGenqlSelection, -} - Assets: { - root: Assets, - selection: AssetsGenqlSelection, -} BaseRichTextJson: { root: BaseRichTextJson, selection: BaseRichTextJsonGenqlSelection, @@ -1689,22 +871,6 @@ export interface FragmentsMap { BlockVideo: { root: BlockVideo, selection: BlockVideoGenqlSelection, -} - Body: { - root: Body, - selection: BodyGenqlSelection, -} - BodyRichText: { - root: BodyRichText, - selection: BodyRichTextGenqlSelection, -} - Collection: { - root: Collection, - selection: CollectionGenqlSelection, -} - Documentation: { - root: Documentation, - selection: DocumentationGenqlSelection, } GetUploadSignedURL: { root: GetUploadSignedURL, @@ -1713,14 +879,6 @@ export interface FragmentsMap { ListMeta: { root: ListMeta, selection: ListMetaGenqlSelection, -} - McpTemplateComponent: { - root: McpTemplateComponent, - selection: McpTemplateComponentGenqlSelection, -} - Mcps: { - root: Mcps, - selection: McpsGenqlSelection, } MediaBlock: { root: MediaBlock, @@ -1745,22 +903,6 @@ export interface FragmentsMap { SearchHighlight: { root: SearchHighlight, selection: SearchHighlightGenqlSelection, -} - Showcase: { - root: Showcase, - selection: ShowcaseGenqlSelection, -} - SidebarTree: { - root: SidebarTree, - selection: SidebarTreeGenqlSelection, -} - SidebarTreeComponent: { - root: SidebarTreeComponent, - selection: SidebarTreeComponentGenqlSelection, -} - Submissions: { - root: Submissions, - selection: SubmissionsGenqlSelection, } TransactionStatus: { root: TransactionStatus, @@ -1769,10 +911,6 @@ export interface FragmentsMap { Variant: { root: Variant, selection: VariantGenqlSelection, -} - _AgentStart: { - root: _AgentStart, - selection: _AgentStartGenqlSelection, } _BranchInfo: { root: _BranchInfo, @@ -1793,25 +931,5 @@ export interface FragmentsMap { _PlaygroundInfo: { root: _PlaygroundInfo, selection: _PlaygroundInfoGenqlSelection, -} - _agents: { - root: _agents, - selection: _agentsGenqlSelection, -} - _components: { - root: _components, - selection: _componentsGenqlSelection, -} - articleComponent_AsList: { - root: articleComponent_AsList, - selection: articleComponent_AsListGenqlSelection, -} - mcpTemplateComponent_AsList: { - root: mcpTemplateComponent_AsList, - selection: mcpTemplateComponent_AsListGenqlSelection, -} - sidebarTreeComponent_AsList: { - root: sidebarTreeComponent_AsList, - selection: sidebarTreeComponent_AsListGenqlSelection, } } diff --git a/apps/website/components/dashboard/download-trend-chart.tsx b/apps/website/components/dashboard/download-trend-chart.tsx new file mode 100644 index 00000000..1e6c964b --- /dev/null +++ b/apps/website/components/dashboard/download-trend-chart.tsx @@ -0,0 +1,117 @@ +'use client'; + +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Tooltip, ResponsiveContainer, Area, AreaChart } from 'recharts'; +import { format } from 'date-fns'; +import { useState, useMemo } from 'react'; +import type { DailyDownload } from '@/lib/types/npm'; + +interface DownloadTrendChartProps { + data: DailyDownload[]; +} + +type TimeRange = '7' | '30' | '90' | '365' | 'all'; + +export function DownloadTrendChart({ data }: DownloadTrendChartProps) { + const [timeRange, setTimeRange] = useState('30'); + + // Filter data based on selected time range + const filteredData = useMemo(() => { + if (timeRange === 'all') { + return data; + } + const days = parseInt(timeRange); + return data.slice(-days); + }, [data, timeRange]); + + // Format data for the chart + const chartData = filteredData.map((item) => ({ + date: format(new Date(item.day), 'MMM dd'), + fullDate: item.day, + downloads: item.downloads, + })); + + // Calculate total for the selected range + const totalDownloads = filteredData.reduce((sum, day) => sum + day.downloads, 0); + + const formatNumber = (num: number) => { + if (num >= 1000000) { + return `${(num / 1000000).toFixed(2)}M`; + } + if (num >= 1000) { + return `${(num / 1000).toFixed(1)}K`; + } + return num.toLocaleString(); + }; + + return ( + + +
+ Download Trend +

+ {formatNumber(totalDownloads)} downloads in the last {timeRange === 'all' ? data.length : timeRange} days +

+
+ setTimeRange(value as TimeRange)}> + + 7D + 30D + 90D + 1Y + All + + +
+ + + + + + + + + + { + if (active && payload && payload.length) { + return ( +
+
+
+ + Date + + + {payload[0].payload.fullDate} + +
+
+ + Downloads + + + {payload[0].value?.toLocaleString()} + +
+
+
+ ); + } + return null; + }} + /> + +
+
+
+
+ ); +} diff --git a/apps/website/components/dashboard/github-stats.tsx b/apps/website/components/dashboard/github-stats.tsx new file mode 100644 index 00000000..95eb98d0 --- /dev/null +++ b/apps/website/components/dashboard/github-stats.tsx @@ -0,0 +1,117 @@ +'use client'; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Star, GitFork, AlertCircle } from 'lucide-react'; +import { useEffect, useState } from 'react'; + +interface GitHubStatsProps { + repositoryUrl?: string | { type: string; url: string }; +} + +interface GitHubRepoData { + stargazers_count: number; + forks_count: number; + open_issues_count: number; +} + +export function GitHubStats({ repositoryUrl }: GitHubStatsProps) { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + + useEffect(() => { + async function fetchGitHubStats() { + if (!repositoryUrl) { + setLoading(false); + return; + } + + try { + // Extract owner/repo from the repository URL + const url = typeof repositoryUrl === 'string' ? repositoryUrl : repositoryUrl.url; + + // Handle different GitHub URL formats + const match = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/); + if (!match) { + setError(true); + setLoading(false); + return; + } + + const [, owner, repo] = match; + const cleanRepo = repo.replace(/\.git$/, ''); + + // Fetch from GitHub API + const response = await fetch(`https://api.github.com/repos/${owner}/${cleanRepo}`); + if (!response.ok) { + setError(true); + setLoading(false); + return; + } + + const data = await response.json(); + setStats(data); + setLoading(false); + } catch (err) { + console.error('Error fetching GitHub stats:', err); + setError(true); + setLoading(false); + } + } + + fetchGitHubStats(); + }, [repositoryUrl]); + + if (!repositoryUrl || error) { + return null; + } + + if (loading) { + return ( + + + GitHub Stats + Loading... + + + ); + } + + if (!stats) { + return null; + } + + return ( + + + GitHub Stats + Repository metrics + + +
+
+ +
+

{stats.stargazers_count.toLocaleString()}

+

Stars

+
+
+
+ +
+

{stats.forks_count.toLocaleString()}

+

Forks

+
+
+
+ +
+

{stats.open_issues_count.toLocaleString()}

+

Open Issues

+
+
+
+
+
+ ); +} diff --git a/apps/website/components/dashboard/metrics-cards.tsx b/apps/website/components/dashboard/metrics-cards.tsx new file mode 100644 index 00000000..f53f61fb --- /dev/null +++ b/apps/website/components/dashboard/metrics-cards.tsx @@ -0,0 +1,134 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { ArrowUpIcon, ArrowDownIcon, PackageIcon, DownloadIcon, TrendingUpIcon, Star } from 'lucide-react'; + + +interface MetricCardProps { + title: string; + value: string | number; + description?: string; + trend?: { + value: number; + label: string; + }; + icon?: React.ReactNode; +} + +function MetricCard({ title, value, description, trend, icon }: MetricCardProps) { + const isPositiveTrend = trend && trend.value > 0; + const isNegativeTrend = trend && trend.value < 0; + + return ( + + + {title} + {icon &&
{icon}
} +
+ +
{value}
+ {description && ( +

{description}

+ )} + {trend && ( +
+ {isPositiveTrend && ( + <> + + +{trend.value}% + + )} + {isNegativeTrend && ( + <> + + {trend.value}% + + )} + {trend.label} +
+ )} +
+
+ ); +} + +interface MetricsCardsProps { + weeklyDownloads: number; + monthlyDownloads: number; + totalDownloads: number; + versionsCount: number; + maintainersCount: number; + weeklyTrend?: number; + monthlyTrend?: number; + githubStars?: number | null; +} + +export function MetricsCards({ + weeklyDownloads, + monthlyDownloads, + totalDownloads, + versionsCount, + weeklyTrend, + monthlyTrend, + githubStars, +}: MetricsCardsProps) { + const formatNumber = (num: number) => { + if (num >= 1000000) { + return `${(num / 1000000).toFixed(2)}M`; + } + if (num >= 1000) { + return `${(num / 1000).toFixed(1)}K`; + } + return num.toLocaleString(); + }; + + // Determine if we have all metrics to show + const hasGithubStars = githubStars !== null && githubStars !== undefined; + + const totalMetrics = 3 + (hasGithubStars ? 1 : 0); + + return ( +
= 5 ? 'lg:grid-cols-3' : 'lg:grid-cols-4'}`}> + } + trend={weeklyTrend !== undefined ? { + value: weeklyTrend, + label: 'vs prev week' + } : undefined} + /> + } + trend={monthlyTrend !== undefined ? { + value: monthlyTrend, + label: 'vs prev month' + } : undefined} + /> + } + /> + {hasGithubStars && ( + } + /> + )} + {!hasGithubStars && ( + } + /> + )} +
+ ); +} diff --git a/apps/website/components/dashboard/package-info.tsx b/apps/website/components/dashboard/package-info.tsx new file mode 100644 index 00000000..def1bdf9 --- /dev/null +++ b/apps/website/components/dashboard/package-info.tsx @@ -0,0 +1,293 @@ +import { Card, CardContent } from '@/components/ui/card'; +import { FileTextIcon, ScaleIcon, PackageIcon, CalendarIcon, UserIcon } from 'lucide-react'; +import { format } from 'date-fns'; + +interface Repository { + type: string; + url: string; + directory?: string; +} + +interface Maintainer { + name?: string; + email?: string; + username?: string; +} + +interface PackageInfoProps { + name: string; + description?: string; + latestVersion: string; + license?: string; + repository?: Repository | string; + homepage?: string; + keywords?: string[]; + lastPublished?: string; + unpackedSize?: number; + versionsCount: number; + maintainers?: Maintainer[]; +} + +export function PackageInfo({ + license, + lastPublished, + unpackedSize, + versionsCount, + maintainers, +}: PackageInfoProps) { + const formatBytes = (bytes?: number) => { + if (!bytes) return 'N/A'; + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + }; + + // Generate Gravatar URL from email + const getMaintainerAvatar = (email?: string): string => { + if (!email) return `https://www.gravatar.com/avatar/00000000000000000000000000000000?s=40&d=retro`; + + // Normalize email (lowercase and trim) + const normalizedEmail = email.toLowerCase().trim(); + + // Generate MD5 hash + const hash = md5(normalizedEmail); + + return `https://www.gravatar.com/avatar/${hash}?s=40&d=retro`; + }; + + // MD5 hash implementation for browser + const md5 = (str: string): string => { + // Simple MD5 implementation for client-side + function rotateLeft(value: number, shift: number): number { + return (value << shift) | (value >>> (32 - shift)); + } + + function addUnsigned(x: number, y: number): number { + const lsw = (x & 0xFFFF) + (y & 0xFFFF); + const msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + } + + function f(x: number, y: number, z: number): number { + return (x & y) | (~x & z); + } + + function g(x: number, y: number, z: number): number { + return (x & z) | (y & ~z); + } + + function h(x: number, y: number, z: number): number { + return x ^ y ^ z; + } + + function i(x: number, y: number, z: number): number { + return y ^ (x | ~z); + } + + function ff(a: number, b: number, c: number, d: number, x: number, s: number, ac: number): number { + a = addUnsigned(a, addUnsigned(addUnsigned(f(b, c, d), x), ac)); + return addUnsigned(rotateLeft(a, s), b); + } + + function gg(a: number, b: number, c: number, d: number, x: number, s: number, ac: number): number { + a = addUnsigned(a, addUnsigned(addUnsigned(g(b, c, d), x), ac)); + return addUnsigned(rotateLeft(a, s), b); + } + + function hh(a: number, b: number, c: number, d: number, x: number, s: number, ac: number): number { + a = addUnsigned(a, addUnsigned(addUnsigned(h(b, c, d), x), ac)); + return addUnsigned(rotateLeft(a, s), b); + } + + function ii(a: number, b: number, c: number, d: number, x: number, s: number, ac: number): number { + a = addUnsigned(a, addUnsigned(addUnsigned(i(b, c, d), x), ac)); + return addUnsigned(rotateLeft(a, s), b); + } + + function convertToWordArray(str: string): number[] { + const wordArray: number[] = []; + const asciiLength = str.length; + + for (let i = 0; i < asciiLength; i++) { + wordArray[i >> 2] |= (str.charCodeAt(i) & 0xFF) << ((i % 4) * 8); + } + + return wordArray; + } + + function wordToHex(value: number): string { + let hex = ''; + for (let i = 0; i < 4; i++) { + const byte = (value >>> (i * 8)) & 0xFF; + hex += byte.toString(16).padStart(2, '0'); + } + return hex; + } + + const x = convertToWordArray(str); + const len = str.length * 8; + + x[len >> 5] |= 0x80 << (len % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + let a = 0x67452301; + let b = 0xEFCDAB89; + let c = 0x98BADCFE; + let d = 0x10325476; + + const S11 = 7, S12 = 12, S13 = 17, S14 = 22; + const S21 = 5, S22 = 9, S23 = 14, S24 = 20; + const S31 = 4, S32 = 11, S33 = 16, S34 = 23; + const S41 = 6, S42 = 10, S43 = 15, S44 = 21; + + for (let k = 0; k < x.length; k += 16) { + const AA = a, BB = b, CC = c, DD = d; + + a = ff(a, b, c, d, x[k + 0], S11, 0xD76AA478); + d = ff(d, a, b, c, x[k + 1], S12, 0xE8C7B756); + c = ff(c, d, a, b, x[k + 2], S13, 0x242070DB); + b = ff(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE); + a = ff(a, b, c, d, x[k + 4], S11, 0xF57C0FAF); + d = ff(d, a, b, c, x[k + 5], S12, 0x4787C62A); + c = ff(c, d, a, b, x[k + 6], S13, 0xA8304613); + b = ff(b, c, d, a, x[k + 7], S14, 0xFD469501); + a = ff(a, b, c, d, x[k + 8], S11, 0x698098D8); + d = ff(d, a, b, c, x[k + 9], S12, 0x8B44F7AF); + c = ff(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1); + b = ff(b, c, d, a, x[k + 11], S14, 0x895CD7BE); + a = ff(a, b, c, d, x[k + 12], S11, 0x6B901122); + d = ff(d, a, b, c, x[k + 13], S12, 0xFD987193); + c = ff(c, d, a, b, x[k + 14], S13, 0xA679438E); + b = ff(b, c, d, a, x[k + 15], S14, 0x49B40821); + + a = gg(a, b, c, d, x[k + 1], S21, 0xF61E2562); + d = gg(d, a, b, c, x[k + 6], S22, 0xC040B340); + c = gg(c, d, a, b, x[k + 11], S23, 0x265E5A51); + b = gg(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA); + a = gg(a, b, c, d, x[k + 5], S21, 0xD62F105D); + d = gg(d, a, b, c, x[k + 10], S22, 0x02441453); + c = gg(c, d, a, b, x[k + 15], S23, 0xD8A1E681); + b = gg(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8); + a = gg(a, b, c, d, x[k + 9], S21, 0x21E1CDE6); + d = gg(d, a, b, c, x[k + 14], S22, 0xC33707D6); + c = gg(c, d, a, b, x[k + 3], S23, 0xF4D50D87); + b = gg(b, c, d, a, x[k + 8], S24, 0x455A14ED); + a = gg(a, b, c, d, x[k + 13], S21, 0xA9E3E905); + d = gg(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8); + c = gg(c, d, a, b, x[k + 7], S23, 0x676F02D9); + b = gg(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A); + + a = hh(a, b, c, d, x[k + 5], S31, 0xFFFA3942); + d = hh(d, a, b, c, x[k + 8], S32, 0x8771F681); + c = hh(c, d, a, b, x[k + 11], S33, 0x6D9D6122); + b = hh(b, c, d, a, x[k + 14], S34, 0xFDE5380C); + a = hh(a, b, c, d, x[k + 1], S31, 0xA4BEEA44); + d = hh(d, a, b, c, x[k + 4], S32, 0x4BDECFA9); + c = hh(c, d, a, b, x[k + 7], S33, 0xF6BB4B60); + b = hh(b, c, d, a, x[k + 10], S34, 0xBEBFBC70); + a = hh(a, b, c, d, x[k + 13], S31, 0x289B7EC6); + d = hh(d, a, b, c, x[k + 0], S32, 0xEAA127FA); + c = hh(c, d, a, b, x[k + 3], S33, 0xD4EF3085); + b = hh(b, c, d, a, x[k + 6], S34, 0x04881D05); + a = hh(a, b, c, d, x[k + 9], S31, 0xD9D4D039); + d = hh(d, a, b, c, x[k + 12], S32, 0xE6DB99E5); + c = hh(c, d, a, b, x[k + 15], S33, 0x1FA27CF8); + b = hh(b, c, d, a, x[k + 2], S34, 0xC4AC5665); + + a = ii(a, b, c, d, x[k + 0], S41, 0xF4292244); + d = ii(d, a, b, c, x[k + 7], S42, 0x432AFF97); + c = ii(c, d, a, b, x[k + 14], S43, 0xAB9423A7); + b = ii(b, c, d, a, x[k + 5], S44, 0xFC93A039); + a = ii(a, b, c, d, x[k + 12], S41, 0x655B59C3); + d = ii(d, a, b, c, x[k + 3], S42, 0x8F0CCC92); + c = ii(c, d, a, b, x[k + 10], S43, 0xFFEFF47D); + b = ii(b, c, d, a, x[k + 1], S44, 0x85845DD1); + a = ii(a, b, c, d, x[k + 8], S41, 0x6FA87E4F); + d = ii(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0); + c = ii(c, d, a, b, x[k + 6], S43, 0xA3014314); + b = ii(b, c, d, a, x[k + 13], S44, 0x4E0811A1); + a = ii(a, b, c, d, x[k + 4], S41, 0xF7537E82); + d = ii(d, a, b, c, x[k + 11], S42, 0xBD3AF235); + c = ii(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB); + b = ii(b, c, d, a, x[k + 9], S44, 0xEB86D391); + + a = addUnsigned(a, AA); + b = addUnsigned(b, BB); + c = addUnsigned(c, CC); + d = addUnsigned(d, DD); + } + + return wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d); + }; + + return ( + + + {/* Package Stats */} +
+
+ +
+

License

+

{license || 'Not specified'}

+
+
+ +
+ +
+

Package Size

+

{formatBytes(unpackedSize)}

+
+
+ +
+ +
+

Last Published

+

+ {lastPublished ? format(new Date(lastPublished), 'MMM dd, yyyy') : 'N/A'} +

+
+
+ +
+ +
+

Total Versions

+

{versionsCount}

+
+
+ + {/* Maintainers as part of stats grid */} + {maintainers && maintainers.length > 0 && ( +
+ +
+

Maintainers

+
+ {maintainers.map((maintainer, index) => ( + + {maintainer.name + {maintainer.name || maintainer.username || maintainer.email || 'Unknown'} + + ))} +
+
+
+ )} +
+ + +
+
+ ); +} diff --git a/apps/website/components/dashboard/version-distribution-chart.tsx b/apps/website/components/dashboard/version-distribution-chart.tsx new file mode 100644 index 00000000..12655437 --- /dev/null +++ b/apps/website/components/dashboard/version-distribution-chart.tsx @@ -0,0 +1,154 @@ +'use client'; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Badge } from '@/components/ui/badge'; +import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, LabelProps } from 'recharts'; + +interface VersionDistributionChartProps { + versions: Record; +} + +const BAR_COLOR = '#ffffff'; // white + +export function VersionDistributionChart({ versions }: VersionDistributionChartProps) { + // Get top 5 versions + const topVersions = Object.entries(versions) + .sort(([, a], [, b]) => b - a) + .slice(0, 5); + + const total = Object.values(versions).reduce((sum, count) => sum + count, 0); + + // Prepare chart data + const chartData = topVersions.map(([version, downloads]) => ({ + version, + downloads, + percentage: ((downloads / total) * 100).toFixed(1), + })); + + return ( +
+ + + Top Versions + Downloads by version (last week) + + + + + + + { + if (active && payload && payload.length) { + return ( +
+
+ + Downloads + + + {payload[0].value?.toLocaleString()} + + + {payload[0].payload.percentage}% of total + +
+
+ ); + } + return null; + }} + /> + { + const version = chartData[index as number]?.version; + const badgeWidth = 60; + const minBarWidth = badgeWidth + 16; // Minimum width to fit badge inside + const barWidth = Number(width); + + // If bar is too small, place badge outside (to the right) + const isOutside = barWidth < minBarWidth; + const badgeX = isOutside + ? Number(x) + barWidth + 8 // Outside: right of bar + : Number(x) + barWidth - badgeWidth - 8; // Inside: at end of bar + + return ( + + + + v{version} + + + ); + } + }} /> +
+
+
+
+ + + + Version Details + Download breakdown by version + + + + + + Version + Downloads + Share + + + + {topVersions.map(([version, downloads], index) => { + const percentage = ((downloads / total) * 100).toFixed(1); + return ( + + + + v{version} + + + + {downloads.toLocaleString()} + + + {percentage}% + + + ); + })} + +
+
+
+
+ ); +} diff --git a/apps/website/components/ui/badge.tsx b/apps/website/components/ui/badge.tsx new file mode 100644 index 00000000..fd3a406b --- /dev/null +++ b/apps/website/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/apps/website/components/ui/card.tsx b/apps/website/components/ui/card.tsx new file mode 100644 index 00000000..681ad980 --- /dev/null +++ b/apps/website/components/ui/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/apps/website/components/ui/table.tsx b/apps/website/components/ui/table.tsx new file mode 100644 index 00000000..51b74dd5 --- /dev/null +++ b/apps/website/components/ui/table.tsx @@ -0,0 +1,116 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Table({ className, ...props }: React.ComponentProps<"table">) { + return ( +
+ + + ) +} + +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { + return ( + + ) +} + +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { + return ( + + ) +} + +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { + return ( + tr]:last:border-b-0", + className + )} + {...props} + /> + ) +} + +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { + return ( + + ) +} + +function TableHead({ className, ...props }: React.ComponentProps<"th">) { + return ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCell({ className, ...props }: React.ComponentProps<"td">) { + return ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">) { + return ( +
+ ) +} + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/apps/website/components/ui/tabs.tsx b/apps/website/components/ui/tabs.tsx new file mode 100644 index 00000000..497ba5ea --- /dev/null +++ b/apps/website/components/ui/tabs.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/apps/website/lib/types/npm.ts b/apps/website/lib/types/npm.ts new file mode 100644 index 00000000..ed0f545e --- /dev/null +++ b/apps/website/lib/types/npm.ts @@ -0,0 +1,195 @@ +// Download statistics types +export interface DownloadPoint { + downloads: number; + start: string; + end: string; + package: string; +} + +export interface DailyDownload { + day: string; + downloads: number; +} + +export interface DownloadRange { + downloads: DailyDownload[]; + start: string; + end: string; + package: string; +} + +export interface VersionDownloads { + package: string; + downloads: Record; +} + +// Package metadata types +export interface PackageVersion { + name: string; + version: string; + description?: string; + keywords?: string[]; + homepage?: string; + bugs?: { + url?: string; + }; + license?: string; + author?: Person | string; + contributors?: (Person | string)[]; + maintainers?: (Person | string)[]; + repository?: Repository | string; + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; + engines?: Record; + dist?: { + tarball: string; + shasum: string; + integrity?: string; + fileCount?: number; + unpackedSize?: number; + }; + _npmUser?: Person; + _npmVersion?: string; + _nodeVersion?: string; +} + +export interface Person { + name: string; + email?: string; + url?: string; + username?: string; +} + +export interface Repository { + type: string; + url: string; + directory?: string; +} + +export interface PackageMetadata { + _id: string; + _rev: string; + name: string; + description?: string; + 'dist-tags': Record; + versions: Record; + time: Record; + maintainers: Person[]; + author?: Person | string; + repository?: Repository | string; + keywords?: string[]; + license?: string; + readme?: string; + readmeFilename?: string; + homepage?: string; + bugs?: { + url?: string; + }; +} + +// Search API types +export interface SearchScore { + final: number; + detail: { + quality: number; + popularity: number; + maintenance: number; + }; +} + +export interface SearchPackage { + name: string; + version: string; + description?: string; + keywords?: string[]; + date?: string; + links?: { + npm?: string; + homepage?: string; + repository?: string; + bugs?: string; + }; + author?: Person; + publisher?: Person; + maintainers?: Person[]; +} + +export interface SearchResult { + package: SearchPackage; + score: SearchScore; + searchScore: number; + downloads?: { + weekly?: number; + monthly?: number; + }; +} + +export interface SearchResponse { + objects: SearchResult[]; + total: number; + time: string; +} + +// Libraries.io API types +export interface LibrariesIODependent { + name: string; + platform: string; + description?: string; + homepage?: string; + repository_url?: string; + normalized_licenses?: string[]; + rank?: number; + latest_release_published_at?: string; + latest_stable_release_number?: string; + language?: string; + status?: string; + package_manager_url?: string; + stars?: number; + forks?: number; + keywords?: string[]; + latest_download_url?: string; + dependents_count?: number; + dependent_repos_count?: number; +} + +export interface LibrariesIODependentsResponse { + dependents_count: number; + dependent_repos_count: number; + dependents: LibrariesIODependent[]; +} + +export interface LibrariesIORepository { + full_name: string; + description?: string; + language?: string; + stars: number; + forks?: number; + homepage?: string; + repository_url: string; + last_pushed?: string; + updated_at?: string; + owner?: { + name?: string; + avatar_url?: string; + }; +} + +export interface LibrariesIORepositoriesResponse { + dependent_repos_count: number; + repositories: LibrariesIORepository[]; +} + +// Combined dashboard data type +export interface DashboardData { + downloads: { + lastWeek: DownloadPoint; + lastMonth: DownloadPoint; + lastYear: DownloadRange; + }; + versions: VersionDownloads; + metadata: PackageMetadata; + search: SearchResult; + dependents?: LibrariesIODependentsResponse; + repositories?: LibrariesIORepositoriesResponse; +} diff --git a/apps/website/package.json b/apps/website/package.json index 4fa2c876..1618edd1 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -41,6 +41,7 @@ "basehub": "^9.0.19", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "framer-motion": "^12.22.0", "fumadocs-core": "^15.8.2", "fumadocs-mdx": "^12.0.1", @@ -58,6 +59,7 @@ "react-dom": "^19.0.0", "react-error-boundary": "^6.0.0", "react-remove-scroll": "^2.7.1", + "recharts": "^3.3.0", "rehype-katex": "^7.0.1", "remark": "^15.0.1", "remark-gfm": "^4.0.1", From 8667188a86850d28429d9af2ba8f0864795e8f97 Mon Sep 17 00:00:00 2001 From: 0xKoller Date: Wed, 5 Nov 2025 12:39:03 -0300 Subject: [PATCH 2/7] chore: added stats to navbar --- apps/website/basehub-types.d.ts | 105 ++++++++++++++++-- .../components/layout/header/index.tsx | 1 + .../components/layout/header/mobile.tsx | 12 ++ 3 files changed, 107 insertions(+), 11 deletions(-) diff --git a/apps/website/basehub-types.d.ts b/apps/website/basehub-types.d.ts index 68e2348a..1950de5d 100644 --- a/apps/website/basehub-types.d.ts +++ b/apps/website/basehub-types.d.ts @@ -99,17 +99,7 @@ export interface BlockColor { __typename: 'BlockColor' } -export interface BlockDocument { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - __typename: string -} +export type BlockDocument = (_AgentStart) & { __isUnion?: true } export interface BlockDocumentSys { apiNamePath: Scalars['String'] @@ -267,6 +257,9 @@ export interface Mutation { } export interface Query { + _agent: (_AgentStart | null) + /** Query across the custom AI agents in the repository. */ + _agents: _agents /** The diff between the current branch and the head commit. */ _diff: Scalars['JSON'] /** The structure of the repository. Used by START. */ @@ -319,6 +312,35 @@ export interface Variant { __typename: 'Variant' } +export interface _AgentStart { + _agentKey: Scalars['String'] + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + accent: Scalars['String'] + avatar: Scalars['String'] + chatUrl: Scalars['String'] + commit: Scalars['Boolean'] + description: Scalars['String'] + edit: Scalars['Boolean'] + embedUrl: Scalars['String'] + getUserInfo: Scalars['Boolean'] + grayscale: Scalars['String'] + manageBranches: Scalars['Boolean'] + mcpUrl: Scalars['String'] + model: Scalars['String'] + openRouterKey: (Scalars['String'] | null) + searchTheWeb: Scalars['Boolean'] + slackInstallUrl: Scalars['String'] + systemPrompt: Scalars['String'] + __typename: '_AgentStart' +} + export interface _BranchInfo { archivedAt: (Scalars['String'] | null) archivedBy: (Scalars['String'] | null) @@ -384,6 +406,11 @@ export type _ResolveTargetsWithEnum = 'id' | 'objectName' export type _StructureFormatEnum = 'json' | 'xml' +export interface _agents { + start: _AgentStart + __typename: '_agents' +} + export interface BaseRichTextJsonGenqlSelection{ blocks?: boolean | number content?: boolean | number @@ -442,6 +469,7 @@ export interface BlockDocumentGenqlSelection{ _slugPath?: boolean | number _sys?: BlockDocumentSysGenqlSelection _title?: boolean | number + on__AgentStart?: _AgentStartGenqlSelection __typename?: boolean | number __fragmentOn?: "BlockDocument" } @@ -674,6 +702,11 @@ export interface MutationGenqlSelection{ export interface NumberFilter {eq?: (Scalars['Float'] | null),gt?: (Scalars['Float'] | null),gte?: (Scalars['Float'] | null),isNull?: (Scalars['Boolean'] | null),lt?: (Scalars['Float'] | null),lte?: (Scalars['Float'] | null),neq?: (Scalars['Float'] | null)} export interface QueryGenqlSelection{ + _agent?: (_AgentStartGenqlSelection & { __args: { + /** The ID of the agent. */ + id: Scalars['String']} }) + /** Query across the custom AI agents in the repository. */ + _agents?: _agentsGenqlSelection /** The diff between the current branch and the head commit. */ _diff?: { __args: { /** Simplified diff returns only the items array showing statuses. */ @@ -757,6 +790,42 @@ export interface VariantGenqlSelection{ __fragmentOn?: "Variant" } +export interface _AgentStartGenqlSelection{ + _agentKey?: boolean | number + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + accent?: boolean | number + avatar?: boolean | number + chatUrl?: boolean | number + commit?: boolean | number + description?: boolean | number + edit?: boolean | number + embedUrl?: boolean | number + getUserInfo?: boolean | number + grayscale?: boolean | number + manageBranches?: boolean | number + mcpUrl?: boolean | number + model?: boolean | number + openRouterKey?: boolean | number + searchTheWeb?: boolean | number + slackInstallUrl?: boolean | number + systemPrompt?: boolean | number + __typename?: boolean | number + __fragmentOn?: "_AgentStart" +} + export interface _BranchInfoGenqlSelection{ archivedAt?: boolean | number archivedBy?: boolean | number @@ -823,6 +892,12 @@ export interface _PlaygroundInfoGenqlSelection{ __fragmentOn?: "_PlaygroundInfo" } +export interface _agentsGenqlSelection{ + start?: _AgentStartGenqlSelection + __typename?: boolean | number + __fragmentOn?: "_agents" +} + export interface FragmentsMap { BaseRichTextJson: { root: BaseRichTextJson, @@ -911,6 +986,10 @@ export interface FragmentsMap { Variant: { root: Variant, selection: VariantGenqlSelection, +} + _AgentStart: { + root: _AgentStart, + selection: _AgentStartGenqlSelection, } _BranchInfo: { root: _BranchInfo, @@ -931,5 +1010,9 @@ export interface FragmentsMap { _PlaygroundInfo: { root: _PlaygroundInfo, selection: _PlaygroundInfoGenqlSelection, +} + _agents: { + root: _agents, + selection: _agentsGenqlSelection, } } diff --git a/apps/website/components/layout/header/index.tsx b/apps/website/components/layout/header/index.tsx index fccac475..d5f893aa 100644 --- a/apps/website/components/layout/header/index.tsx +++ b/apps/website/components/layout/header/index.tsx @@ -21,6 +21,7 @@ export const Header = () => { Examples Blog Showcase + Stats
diff --git a/apps/website/components/layout/header/mobile.tsx b/apps/website/components/layout/header/mobile.tsx index aca7f4e7..4ccfcadd 100644 --- a/apps/website/components/layout/header/mobile.tsx +++ b/apps/website/components/layout/header/mobile.tsx @@ -105,6 +105,18 @@ export const MobileMenu = () => { Blog
+
+ + Stats + +
From f6d487b3080e964270e03ecaeac13af48fc96451 Mon Sep 17 00:00:00 2001 From: 0xKoller Date: Wed, 5 Nov 2025 12:57:04 -0300 Subject: [PATCH 3/7] chore: updated skeletons --- apps/website/app/stats/page.tsx | 62 ++++++++--- apps/website/basehub-types.d.ts | 105 ++---------------- .../components/dashboard/metrics-cards.tsx | 30 ++--- 3 files changed, 74 insertions(+), 123 deletions(-) diff --git a/apps/website/app/stats/page.tsx b/apps/website/app/stats/page.tsx index db448c44..74a72e08 100644 --- a/apps/website/app/stats/page.tsx +++ b/apps/website/app/stats/page.tsx @@ -86,8 +86,15 @@ async function getDashboardData() { const prevMonthStart = new Date(); prevMonthStart.setDate(prevMonthStart.getDate() - 60); + // For 90-day downloads + const last90DaysEnd = new Date(); + last90DaysEnd.setDate(last90DaysEnd.getDate() - 1); + const last90DaysStart = new Date(); + last90DaysStart.setDate(last90DaysStart.getDate() - 90); + const prevWeekRange = `${formatDate(prevWeekStart)}:${formatDate(prevWeekEnd)}`; const prevMonthRange = `${formatDate(prevMonthStart)}:${formatDate(prevMonthEnd)}`; + const last90DaysRange = `${formatDate(last90DaysStart)}:${formatDate(last90DaysEnd)}`; // Create date chunks for all-time data (npm API limit: 18 months per request) const dateChunks = createDateChunks(startDate, endDate); @@ -113,6 +120,10 @@ async function getDashboardData() { next: { revalidate: 3600 }, cache: 'force-cache' }), + fetch(`${NPM_DOWNLOADS_API}/range/${last90DaysRange}/${PACKAGE_NAME}`, { + next: { revalidate: 3600 }, + cache: 'force-cache' + }), ...dateChunks.map(chunk => fetch(`${NPM_DOWNLOADS_API}/range/${chunk.start}:${chunk.end}/${PACKAGE_NAME}`, { next: { revalidate: 3600 }, @@ -129,13 +140,14 @@ async function getDashboardData() { } // Parse responses - const [weeklyRes, monthlyRes, versionsRes, prevWeekRes, prevMonthRes, ...chunkResponses] = responses; + const [weeklyRes, monthlyRes, versionsRes, prevWeekRes, prevMonthRes, last90DaysRes, ...chunkResponses] = responses; - const [weekly, monthly, versions, prevWeek, prevMonth]: [ + const [weekly, monthly, versions, prevWeek, prevMonth, last90Days]: [ DownloadPoint, DownloadPoint, VersionDownloads, DownloadRange, + DownloadRange, DownloadRange ] = await Promise.all([ weeklyRes.json(), @@ -143,6 +155,7 @@ async function getDashboardData() { versionsRes.json(), prevWeekRes.json(), prevMonthRes.json(), + last90DaysRes.json(), ]); // Parse and combine all chunk data @@ -189,6 +202,7 @@ async function getDashboardData() { // Calculate trends const prevWeekTotal = prevWeek.downloads.reduce((sum, day) => sum + day.downloads, 0); const prevMonthTotal = prevMonth.downloads.reduce((sum, day) => sum + day.downloads, 0); + const last90DaysTotal = last90Days.downloads.reduce((sum, day) => sum + day.downloads, 0); const weeklyTrend = prevWeekTotal > 0 ? Math.round(((weekly.downloads - prevWeekTotal) / prevWeekTotal) * 100) @@ -241,6 +255,7 @@ async function getDashboardData() { return { weekly, monthly, + last90Days: last90DaysTotal, range, metadata, versions, @@ -276,26 +291,40 @@ function MetricCardSkeleton() { } async function VersionBadge() { - const res = await fetch('https://registry.npmjs.org/xmcp', { - next: { revalidate: 3600 }, - cache: 'force-cache' - }); - const data = await res.json(); - const latestVersion = data['dist-tags']?.latest || ''; - - return ( - - v{latestVersion} - - ); + try { + const res = await fetch('https://registry.npmjs.org/xmcp', { + next: { revalidate: 3600 }, + cache: 'force-cache' + }); + + if (!res.ok) { + throw new Error('Failed to fetch version data'); + } + + const data = await res.json(); + const latestVersion = data['dist-tags']?.latest || ''; + + return ( + + v{latestVersion} + + ); + } catch (error) { + console.error('Error fetching version badge:', error); + return ( + + N/A + + ); + } } function DashboardSkeleton() { return (
{/* Metrics Cards */} -
- {[...Array(4)].map((_, i) => ( +
+ {[...Array(6)].map((_, i) => ( ))}
@@ -416,6 +445,7 @@ async function DashboardContent() { = 5 ? 'lg:grid-cols-3' : 'lg:grid-cols-4'}`}> +
} /> + } + /> {hasGithubStars && ( } /> )} - {!hasGithubStars && ( - } - /> - )} + } + />
); } From 97a37a8a989b9b127c1b8071d8de5823e1fc930c Mon Sep 17 00:00:00 2001 From: 0xKoller Date: Wed, 5 Nov 2025 12:59:42 -0300 Subject: [PATCH 4/7] fix: animated link sequential animation for stats --- apps/website/components/layout/header/mobile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/components/layout/header/mobile.tsx b/apps/website/components/layout/header/mobile.tsx index 4ccfcadd..05c4148d 100644 --- a/apps/website/components/layout/header/mobile.tsx +++ b/apps/website/components/layout/header/mobile.tsx @@ -107,7 +107,7 @@ export const MobileMenu = () => {
Date: Wed, 5 Nov 2025 13:35:05 -0300 Subject: [PATCH 5/7] fix: og dynamic railguards --- apps/website/app/stats/opengraph-image.tsx | 22 +++++ apps/website/basehub-types.d.ts | 105 ++++++++++++++++++--- 2 files changed, 116 insertions(+), 11 deletions(-) diff --git a/apps/website/app/stats/opengraph-image.tsx b/apps/website/app/stats/opengraph-image.tsx index 29666294..45fd3979 100644 --- a/apps/website/app/stats/opengraph-image.tsx +++ b/apps/website/app/stats/opengraph-image.tsx @@ -163,6 +163,17 @@ export default async function Image() { {/* Generate smooth path using Catmull-Rom interpolation */} { + // Handle empty or insufficient data first + if (normalizedData.length === 0) { + return 'M 20 530 L 680 530 Z'; // Flat line at bottom + } + + if (normalizedData.length === 1) { + // Single point - create a small area at center + const y = 530 - Math.max(normalizedData[0].heightPercent * 4.5, 50); + return `M 350 ${y} L 350 530 Z`; + } + const points = normalizedData.map((day, i) => ({ x: 20 + (i * (660 / (normalizedData.length - 1))), y: 530 - Math.max(day.heightPercent * 4.5, 50), @@ -196,6 +207,17 @@ export default async function Image() { {/* Smooth line stroke */} { + // Handle empty or insufficient data first + if (normalizedData.length === 0) { + return 'M 20 530 L 680 530'; // Flat line at bottom + } + + if (normalizedData.length === 1) { + // Single point - just show the point at center + const y = 530 - Math.max(normalizedData[0].heightPercent * 4.5, 50); + return `M 350 ${y} L 350 ${y}`; + } + const points = normalizedData.map((day, i) => ({ x: 20 + (i * (660 / (normalizedData.length - 1))), y: 530 - Math.max(day.heightPercent * 4.5, 50), diff --git a/apps/website/basehub-types.d.ts b/apps/website/basehub-types.d.ts index 68e2348a..1950de5d 100644 --- a/apps/website/basehub-types.d.ts +++ b/apps/website/basehub-types.d.ts @@ -99,17 +99,7 @@ export interface BlockColor { __typename: 'BlockColor' } -export interface BlockDocument { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - __typename: string -} +export type BlockDocument = (_AgentStart) & { __isUnion?: true } export interface BlockDocumentSys { apiNamePath: Scalars['String'] @@ -267,6 +257,9 @@ export interface Mutation { } export interface Query { + _agent: (_AgentStart | null) + /** Query across the custom AI agents in the repository. */ + _agents: _agents /** The diff between the current branch and the head commit. */ _diff: Scalars['JSON'] /** The structure of the repository. Used by START. */ @@ -319,6 +312,35 @@ export interface Variant { __typename: 'Variant' } +export interface _AgentStart { + _agentKey: Scalars['String'] + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + accent: Scalars['String'] + avatar: Scalars['String'] + chatUrl: Scalars['String'] + commit: Scalars['Boolean'] + description: Scalars['String'] + edit: Scalars['Boolean'] + embedUrl: Scalars['String'] + getUserInfo: Scalars['Boolean'] + grayscale: Scalars['String'] + manageBranches: Scalars['Boolean'] + mcpUrl: Scalars['String'] + model: Scalars['String'] + openRouterKey: (Scalars['String'] | null) + searchTheWeb: Scalars['Boolean'] + slackInstallUrl: Scalars['String'] + systemPrompt: Scalars['String'] + __typename: '_AgentStart' +} + export interface _BranchInfo { archivedAt: (Scalars['String'] | null) archivedBy: (Scalars['String'] | null) @@ -384,6 +406,11 @@ export type _ResolveTargetsWithEnum = 'id' | 'objectName' export type _StructureFormatEnum = 'json' | 'xml' +export interface _agents { + start: _AgentStart + __typename: '_agents' +} + export interface BaseRichTextJsonGenqlSelection{ blocks?: boolean | number content?: boolean | number @@ -442,6 +469,7 @@ export interface BlockDocumentGenqlSelection{ _slugPath?: boolean | number _sys?: BlockDocumentSysGenqlSelection _title?: boolean | number + on__AgentStart?: _AgentStartGenqlSelection __typename?: boolean | number __fragmentOn?: "BlockDocument" } @@ -674,6 +702,11 @@ export interface MutationGenqlSelection{ export interface NumberFilter {eq?: (Scalars['Float'] | null),gt?: (Scalars['Float'] | null),gte?: (Scalars['Float'] | null),isNull?: (Scalars['Boolean'] | null),lt?: (Scalars['Float'] | null),lte?: (Scalars['Float'] | null),neq?: (Scalars['Float'] | null)} export interface QueryGenqlSelection{ + _agent?: (_AgentStartGenqlSelection & { __args: { + /** The ID of the agent. */ + id: Scalars['String']} }) + /** Query across the custom AI agents in the repository. */ + _agents?: _agentsGenqlSelection /** The diff between the current branch and the head commit. */ _diff?: { __args: { /** Simplified diff returns only the items array showing statuses. */ @@ -757,6 +790,42 @@ export interface VariantGenqlSelection{ __fragmentOn?: "Variant" } +export interface _AgentStartGenqlSelection{ + _agentKey?: boolean | number + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + accent?: boolean | number + avatar?: boolean | number + chatUrl?: boolean | number + commit?: boolean | number + description?: boolean | number + edit?: boolean | number + embedUrl?: boolean | number + getUserInfo?: boolean | number + grayscale?: boolean | number + manageBranches?: boolean | number + mcpUrl?: boolean | number + model?: boolean | number + openRouterKey?: boolean | number + searchTheWeb?: boolean | number + slackInstallUrl?: boolean | number + systemPrompt?: boolean | number + __typename?: boolean | number + __fragmentOn?: "_AgentStart" +} + export interface _BranchInfoGenqlSelection{ archivedAt?: boolean | number archivedBy?: boolean | number @@ -823,6 +892,12 @@ export interface _PlaygroundInfoGenqlSelection{ __fragmentOn?: "_PlaygroundInfo" } +export interface _agentsGenqlSelection{ + start?: _AgentStartGenqlSelection + __typename?: boolean | number + __fragmentOn?: "_agents" +} + export interface FragmentsMap { BaseRichTextJson: { root: BaseRichTextJson, @@ -911,6 +986,10 @@ export interface FragmentsMap { Variant: { root: Variant, selection: VariantGenqlSelection, +} + _AgentStart: { + root: _AgentStart, + selection: _AgentStartGenqlSelection, } _BranchInfo: { root: _BranchInfo, @@ -931,5 +1010,9 @@ export interface FragmentsMap { _PlaygroundInfo: { root: _PlaygroundInfo, selection: _PlaygroundInfoGenqlSelection, +} + _agents: { + root: _agents, + selection: _agentsGenqlSelection, } } From f264e7fb7adf4483ff7c55f8ee987ab97e474338 Mon Sep 17 00:00:00 2001 From: 0xKoller Date: Wed, 5 Nov 2025 13:40:29 -0300 Subject: [PATCH 6/7] fix: og dynamic railguards --- apps/website/app/stats/opengraph-image.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/website/app/stats/opengraph-image.tsx b/apps/website/app/stats/opengraph-image.tsx index 45fd3979..3013a395 100644 --- a/apps/website/app/stats/opengraph-image.tsx +++ b/apps/website/app/stats/opengraph-image.tsx @@ -179,6 +179,16 @@ export default async function Image() { y: 530 - Math.max(day.heightPercent * 4.5, 50), })); + if (normalizedData.length === 2) { + // Two points - draw a simple quadratic curve + const controlX = (points[0].x + points[1].x) / 2; + const controlY = (points[0].y + points[1].y) / 2; + let path = `M ${points[0].x} ${points[0].y}`; + path += ` Q ${controlX} ${controlY}, ${points[1].x} ${points[1].y}`; + path += ` L ${680} 530 L ${20} 530 Z`; + return path; + } + // Generate smooth curve using quadratic bezier curves let path = `M ${points[0].x} ${points[0].y}`; @@ -223,6 +233,15 @@ export default async function Image() { y: 530 - Math.max(day.heightPercent * 4.5, 50), })); + if (normalizedData.length === 2) { + // Two points - draw a simple quadratic curve + const controlX = (points[0].x + points[1].x) / 2; + const controlY = (points[0].y + points[1].y) / 2; + let path = `M ${points[0].x} ${points[0].y}`; + path += ` Q ${controlX} ${controlY}, ${points[1].x} ${points[1].y}`; + return path; + } + let path = `M ${points[0].x} ${points[0].y}`; for (let i = 0; i < points.length - 1; i++) { From 3d06a1310fdf5716ee46dd4d1cd480686b1aef40 Mon Sep 17 00:00:00 2001 From: 0xKoller Date: Wed, 5 Nov 2025 13:53:38 -0300 Subject: [PATCH 7/7] fix: pnpm lock fix --- pnpm-lock.yaml | 396 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 317 insertions(+), 79 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4d99ce3..eeaa03cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,10 +79,10 @@ importers: version: 3.2.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@react-three/drei': specifier: ^10.4.2 - version: 10.7.6(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0))(@types/react@19.2.2)(@types/three@0.178.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0) + version: 10.7.6(@react-three/fiber@9.4.0(@types/react@19.2.2)(immer@10.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0))(@types/react@19.2.2)(@types/three@0.178.1)(immer@10.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0) '@react-three/fiber': specifier: ^9.1.4 - version: 9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0) + version: 9.4.0(@types/react@19.2.2)(immer@10.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0) '@shikijs/langs': specifier: ^3.7.0 version: 3.13.0 @@ -116,6 +116,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 framer-motion: specifier: ^12.22.0 version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -167,6 +170,9 @@ importers: react-remove-scroll: specifier: ^2.7.1 version: 2.7.1(@types/react@19.2.2)(react@19.2.0) + recharts: + specifier: ^3.3.0 + version: 3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@16.13.1)(react@19.2.0)(redux@5.0.1) rehype-katex: specifier: ^7.0.1 version: 7.0.1 @@ -202,7 +208,7 @@ importers: version: 4.1.12 zustand: specifier: ^5.0.6 - version: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + version: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -796,10 +802,6 @@ importers: react-router-dom: specifier: ^7.7.1 version: 7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - optionalDependencies: - pg: - specifier: ^8.16.3 - version: 8.16.3 devDependencies: '@types/better-sqlite3': specifier: ^7.6.13 @@ -852,6 +854,10 @@ importers: xmcp: specifier: workspace:* version: link:../../xmcp + optionalDependencies: + pg: + specifier: ^8.16.3 + version: 8.16.3 packages/plugins/polar: devDependencies: @@ -2812,6 +2818,17 @@ packages: peerDependencies: '@redis/client': ^1.0.0 + '@reduxjs/toolkit@2.10.1': + resolution: {integrity: sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2990,6 +3007,9 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@stitches/react@1.2.8': resolution: {integrity: sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==} peerDependencies: @@ -3191,6 +3211,33 @@ packages: '@types/content-type@1.1.9': resolution: {integrity: sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -3345,6 +3392,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/webpack-node-externals@3.0.4': resolution: {integrity: sha512-8Z3/edqxE3RRlOJwKSgOFxLZRt/i1qFlv/Bi308ZUKo9jh8oGngd9r8GR0ZNKW5AEJq8QNQE3b17CwghTjQ0Uw==} @@ -4177,6 +4227,50 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -4192,6 +4286,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -4217,6 +4314,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -4397,6 +4497,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es-toolkit@1.41.0: + resolution: {integrity: sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -5191,6 +5294,9 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -5221,6 +5327,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -6558,6 +6668,18 @@ packages: peerDependencies: react: ^19.0.0 + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -6644,6 +6766,14 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + recharts@3.3.0: + resolution: {integrity: sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} @@ -6661,6 +6791,14 @@ packages: redis@4.7.1: resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -6715,6 +6853,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -7198,6 +7339,9 @@ packages: through2@0.6.5: resolution: {integrity: sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyexec@1.0.1: resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} @@ -7499,6 +7643,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + vite@5.4.21: resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -9527,12 +9674,12 @@ snapshots: '@types/react': 19.2.2 '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@react-three/drei@10.7.6(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0))(@types/react@19.2.2)(@types/three@0.178.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0)': + '@react-three/drei@10.7.6(@react-three/fiber@9.4.0(@types/react@19.2.2)(immer@10.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0))(@types/react@19.2.2)(@types/three@0.178.1)(immer@10.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0)': dependencies: '@babel/runtime': 7.28.4 '@mediapipe/tasks-vision': 0.10.17 '@monogrid/gainmap-js': 3.1.0(three@0.178.0) - '@react-three/fiber': 9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0) + '@react-three/fiber': 9.4.0(@types/react@19.2.2)(immer@10.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0) '@use-gesture/react': 10.3.1(react@19.2.0) camera-controls: 3.1.0(three@0.178.0) cross-env: 7.0.3 @@ -9549,10 +9696,10 @@ snapshots: three-mesh-bvh: 0.8.3(three@0.178.0) three-stdlib: 2.36.0(three@0.178.0) troika-three-text: 0.52.4(three@0.178.0) - tunnel-rat: 0.1.2(@types/react@19.2.2)(react@19.2.0) + tunnel-rat: 0.1.2(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0) use-sync-external-store: 1.6.0(react@19.2.0) utility-types: 3.11.0 - zustand: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + zustand: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) optionalDependencies: react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: @@ -9560,7 +9707,7 @@ snapshots: - '@types/three' - immer - '@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0)': + '@react-three/fiber@9.4.0(@types/react@19.2.2)(immer@10.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.178.0)': dependencies: '@babel/runtime': 7.28.4 '@types/react-reconciler': 0.32.2(@types/react@19.2.2) @@ -9575,7 +9722,7 @@ snapshots: suspend-react: 0.1.3(react@19.2.0) three: 0.178.0 use-sync-external-store: 1.6.0(react@19.2.0) - zustand: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + zustand: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) optionalDependencies: react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: @@ -9608,6 +9755,18 @@ snapshots: dependencies: '@redis/client': 1.6.1 + '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': + dependencies: + '@standard-schema/spec': 1.0.0 + '@standard-schema/utils': 0.3.0 + immer: 10.2.0 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.0 + react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.52.5': @@ -9775,6 +9934,8 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@standard-schema/utils@0.3.0': {} + '@stitches/react@1.2.8(react@19.2.0)': dependencies: react: 19.2.0 @@ -9947,6 +10108,30 @@ snapshots: '@types/content-type@1.1.9': {} + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -10134,6 +10319,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/webpack-node-externals@3.0.4(@swc/core@1.11.24)': dependencies: '@types/node': 22.18.12 @@ -11039,6 +11226,44 @@ snapshots: csstype@3.1.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.0: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + damerau-levenshtein@1.0.8: {} data-view-buffer@1.0.2: @@ -11059,6 +11284,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns@4.1.0: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -11071,6 +11298,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -11307,6 +11536,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.41.0: {} + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -11393,7 +11624,7 @@ snapshots: eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.38.0(jiti@2.6.1)) @@ -11412,8 +11643,8 @@ snapshots: '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.38.0(jiti@2.6.1)) @@ -11442,21 +11673,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3 - eslint: 9.38.0(jiti@2.6.1) - get-tsconfig: 4.12.0 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.15 - unrs-resolver: 1.11.1 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) - transitivePeerDependencies: - - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -11468,22 +11684,11 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.38.0(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -11494,7 +11699,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -11505,36 +11710,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 9.38.0(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -12481,6 +12657,8 @@ snapshots: immediate@3.0.6: {} + immer@10.2.0: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -12522,6 +12700,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@2.0.3: {} + ipaddr.js@1.9.1: {} is-alphabetical@2.0.1: {} @@ -14117,6 +14297,15 @@ snapshots: react: 19.2.0 scheduler: 0.25.0 + react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + redux: 5.0.1 + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0): @@ -14204,6 +14393,26 @@ snapshots: readdirp@4.1.2: {} + recharts@3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@16.13.1)(react@19.2.0)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.41.0 + eventemitter3: 5.0.1 + immer: 10.2.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-is: 16.13.1 + react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.0) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + recma-build-jsx@1.0.0: dependencies: '@types/estree': 1.0.8 @@ -14242,6 +14451,12 @@ snapshots: '@redis/search': 1.2.0(@redis/client@1.6.1) '@redis/time-series': 1.1.0(@redis/client@1.6.1) + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.10: @@ -14348,6 +14563,8 @@ snapshots: require-from-string@2.0.2: {} + reselect@5.1.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -14995,6 +15212,8 @@ snapshots: readable-stream: 1.0.34 xtend: 4.0.2 + tiny-invariant@1.3.3: {} + tinyexec@1.0.1: {} tinyglobby@0.2.15: @@ -15058,9 +15277,9 @@ snapshots: dependencies: tslib: 1.14.1 - tunnel-rat@0.1.2(@types/react@19.2.2)(react@19.2.0): + tunnel-rat@0.1.2(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0): dependencies: - zustand: 4.5.7(@types/react@19.2.2)(react@19.2.0) + zustand: 4.5.7(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer @@ -15342,6 +15561,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + vite@5.4.21(@types/node@22.18.12)(lightningcss@1.30.2)(terser@5.44.0): dependencies: esbuild: 0.21.5 @@ -15541,16 +15777,18 @@ snapshots: optionalDependencies: react: 19.2.0 - zustand@4.5.7(@types/react@19.2.2)(react@19.2.0): + zustand@4.5.7(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0): dependencies: use-sync-external-store: 1.6.0(react@19.2.0) optionalDependencies: '@types/react': 19.2.2 + immer: 10.2.0 react: 19.2.0 - zustand@5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + zustand@5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): optionalDependencies: '@types/react': 19.2.2 + immer: 10.2.0 react: 19.2.0 use-sync-external-store: 1.6.0(react@19.2.0)