-
+ `}
+ >
{isDragging ? 'Drop your certificate here' : 'Drag & drop certificate'}
@@ -1284,5 +1290,3 @@ function ComplianceFramework({
>
);
}
-
-
diff --git a/apps/app/src/app/(app)/[orgId]/trust/portal-settings/page.tsx b/apps/app/src/app/(app)/[orgId]/trust/portal-settings/page.tsx
index f128368ba..132982a37 100644
--- a/apps/app/src/app/(app)/[orgId]/trust/portal-settings/page.tsx
+++ b/apps/app/src/app/(app)/[orgId]/trust/portal-settings/page.tsx
@@ -1,10 +1,10 @@
-import PageCore from '@/components/pages/PageCore.tsx';
-import type { Metadata } from 'next';
import { auth } from '@/utils/auth';
-import { env } from '@/env.mjs';
import { db } from '@db';
import { Prisma } from '@prisma/client';
+import type { Metadata } from 'next';
import { headers } from 'next/headers';
+import { env } from '@/env.mjs';
+import PageCore from '@/components/pages/PageCore.tsx';
import { TrustPortalSwitch } from './components/TrustPortalSwitch';
export default async function PortalSettingsPage({ params }: { params: Promise<{ orgId: string }> }) {
@@ -308,7 +308,7 @@ async function fetchComplianceCertificates(orgId: string): Promise {
+async function fetchOrganizationFaqs(orgId: string): Promise {
try {
const organization = await db.organization.findUnique({
where: { id: orgId },
@@ -357,4 +357,3 @@ export async function generateMetadata(): Promise {
title: 'Portal Settings',
};
}
-
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx
index e96dd6887..7d8ce737d 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx
@@ -1,23 +1,55 @@
'use client';
-import { DataTable } from '@/components/data-table/data-table';
-import { DataTableToolbar } from '@/components/data-table/data-table-toolbar';
import { OnboardingLoadingAnimation } from '@/components/onboarding-loading-animation';
-import { useDataTable } from '@/hooks/use-data-table';
-import { getFiltersStateParser, getSortingStateParser } from '@/lib/parsers';
-import { Departments, Vendor } from '@db';
-import { ColumnDef } from '@tanstack/react-table';
-import { Loader2 } from 'lucide-react';
-import { parseAsInteger, parseAsString, parseAsStringEnum, useQueryState } from 'nuqs';
-import { useCallback, useMemo } from 'react';
+import { VendorStatus } from '@/components/vendor-status';
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+ Badge,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+ Empty,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyTitle,
+ HStack,
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+ Spinner,
+ Stack,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+ Text,
+} from '@trycompai/design-system';
+import { OverflowMenuVertical, Search, TrashCan, View } from '@trycompai/design-system/icons';
+import { ArrowDown, ArrowUp, ArrowUpDown, Loader2, UserIcon } from 'lucide-react';
+import { useRouter } from 'next/navigation';
+import { useCallback, useMemo, useState } from 'react';
+import { toast } from 'sonner';
import useSWR from 'swr';
-import { CreateVendorSheet } from '../../components/create-vendor-sheet';
+import { deleteVendor } from '../actions/deleteVendor';
import { getVendorsAction, type GetVendorsActionInput } from '../actions/get-vendors-action';
import type { GetAssigneesResult, GetVendorsResult } from '../data/queries';
import type { GetVendorsSchema } from '../data/validations';
import { useOnboardingStatus } from '../hooks/use-onboarding-status';
-import { VendorOnboardingProvider } from './vendor-onboarding-context';
-import { columns as getColumns } from './VendorColumns';
+import { VendorOnboardingProvider, useVendorOnboardingStatus } from './vendor-onboarding-context';
export type VendorRow = GetVendorsResult['data'][number] & {
isPending?: boolean;
@@ -35,6 +67,17 @@ const ACTIVE_STATUSES: Array<'pending' | 'processing' | 'created' | 'assessing'>
'assessing',
];
+const CATEGORY_MAP: Record = {
+ cloud: 'Cloud',
+ infrastructure: 'Infrastructure',
+ software_as_a_service: 'SaaS',
+ finance: 'Finance',
+ marketing: 'Marketing',
+ sales: 'Sales',
+ hr: 'HR',
+ other: 'Other',
+};
+
interface VendorsTableProps {
vendors: GetVendorsResult['data'];
pageCount: number;
@@ -44,61 +87,99 @@ interface VendorsTableProps {
orgId: string;
}
+function VendorNameCell({ vendor, orgId }: { vendor: VendorRow; orgId: string }) {
+ const onboardingStatus = useVendorOnboardingStatus();
+ const status = onboardingStatus[vendor.id];
+ const isPending = vendor.isPending || status === 'pending' || status === 'processing';
+ const isAssessing = vendor.isAssessing || status === 'assessing';
+ const isResolved = vendor.status === 'assessed';
+
+ if ((isPending || isAssessing) && !isResolved) {
+ return (
+
+
+ {vendor.name}
+
+ );
+ }
+
+ return {vendor.name};
+}
+
+function VendorStatusCell({ vendor }: { vendor: VendorRow }) {
+ const onboardingStatus = useVendorOnboardingStatus();
+ const status = onboardingStatus[vendor.id];
+ const isPending = vendor.isPending || status === 'pending' || status === 'processing';
+ const isAssessing = vendor.isAssessing || status === 'assessing';
+ const isResolved = vendor.status === 'assessed';
+
+ if (isPending && !isResolved) {
+ return (
+
+
+
+ Creating...
+
+
+ );
+ }
+
+ if (isAssessing && !isResolved) {
+ return (
+
+
+
+ Assessing...
+
+
+ );
+ }
+
+ return ;
+}
+
export function VendorsTable({
vendors: initialVendors,
pageCount: initialPageCount,
assignees,
onboardingRunId,
- searchParams: initialSearchParams,
orgId,
}: VendorsTableProps) {
+ const router = useRouter();
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+ const [vendorToDelete, setVendorToDelete] = useState(null);
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ // Local state for search and sorting
+ const [searchQuery, setSearchQuery] = useState('');
+ const [sort, setSort] = useState<{ id: 'name' | 'updatedAt'; desc: boolean }>({
+ id: 'name',
+ desc: false,
+ });
+
const { itemStatuses, progress, itemsInfo, isActive, isLoading } = useOnboardingStatus(
onboardingRunId,
'vendors',
);
- // Read current search params from URL (synced with table state via useDataTable)
- const [page] = useQueryState('page', parseAsInteger.withDefault(1));
- const [perPage] = useQueryState('perPage', parseAsInteger.withDefault(50));
- const [name] = useQueryState('name', parseAsString.withDefault(''));
- const [status] = useQueryState(
- 'status',
- parseAsStringEnum(['not_assessed', 'assessed'] as const),
- );
- const [department] = useQueryState(
- 'department',
- parseAsStringEnum(Object.values(Departments)),
- );
- const [assigneeId] = useQueryState('assigneeId', parseAsString);
- const [sort] = useQueryState(
- 'sort',
- getSortingStateParser().withDefault([{ id: 'name', desc: false }]),
- );
- const [filters] = useQueryState('filters', getFiltersStateParser().withDefault([]));
- const [joinOperator] = useQueryState(
- 'joinOperator',
- parseAsStringEnum(['and', 'or']).withDefault('and'),
- );
-
- // Build current search params from URL state
+ // Build search params for API - only page, perPage for now
const currentSearchParams = useMemo(() => {
return {
- page,
- perPage,
- name,
- status: status ?? null,
- department: department ?? null,
- assigneeId: assigneeId ?? null,
- sort,
- filters,
- joinOperator,
+ page: 1,
+ perPage: 50,
+ name: '',
+ status: null,
+ department: null,
+ assigneeId: null,
+ sort: [{ id: sort.id, desc: sort.desc }],
+ filters: [],
+ joinOperator: 'and',
};
- }, [page, perPage, name, status, department, assigneeId, sort, filters, joinOperator]);
+ }, [sort]);
- // Create stable SWR key from current search params
+ // Create stable SWR key
const swrKey = useMemo(() => {
if (!orgId) return null;
- // Serialize search params to create a stable key
const key = JSON.stringify(currentSearchParams);
return ['vendors', orgId, key] as const;
}, [orgId, currentSearchParams]);
@@ -110,58 +191,47 @@ export function VendorsTable({
}, [orgId, currentSearchParams]);
// Use SWR to fetch vendors with polling for real-time updates
- // Poll faster during onboarding, slower otherwise
const { data: vendorsData } = useSWR(swrKey, fetcher, {
fallbackData: { data: initialVendors, pageCount: initialPageCount },
- refreshInterval: isActive ? 1000 : 5000, // 1s during onboarding, 5s otherwise
+ refreshInterval: isActive ? 1000 : 5000,
revalidateOnFocus: false,
revalidateOnReconnect: true,
keepPreviousData: true,
});
const vendors = vendorsData?.data || initialVendors;
- const pageCount = vendorsData?.pageCount ?? initialPageCount;
// Check if all vendors are done assessing
const allVendorsDoneAssessing = useMemo(() => {
- // If no vendors exist yet, we're not done
if (vendors.length === 0) {
- // But check if there are vendors in metadata that should exist
if (itemsInfo.length > 0) return false;
return false;
}
- // Check if we're still creating vendors by comparing DB count with expected total
- // If progress.total exists and vendors.length < progress.total, we're still creating
if (progress && vendors.length < progress.total) {
return false;
}
- // If there are pending/processing vendors in metadata that aren't in DB yet, we're not done
const hasPendingVendors = itemsInfo.some((item) => {
- const status = itemStatuses[item.id];
+ const itemStatus = itemStatuses[item.id];
return (
- (status === 'pending' ||
- status === 'processing' ||
- status === 'created' ||
- status === 'assessing') &&
+ (itemStatus === 'pending' ||
+ itemStatus === 'processing' ||
+ itemStatus === 'created' ||
+ itemStatus === 'assessing') &&
!vendors.some((v) => v.id === item.id)
);
});
if (hasPendingVendors) return false;
- // Check if all vendors in DB are either:
- // 1. Completed in metadata (status === 'completed')
- // 2. Assessed in database (status === 'assessed')
const allDbVendorsDone = vendors.every((vendor) => {
const metadataStatus = itemStatuses[vendor.id];
return metadataStatus === 'completed' || vendor.status === 'assessed';
});
- // Also check if there are any vendors in metadata that are still assessing
const hasAssessingVendors = Object.values(itemStatuses).some(
- (status) => status === 'assessing' || status === 'processing',
+ (s) => s === 'assessing' || s === 'processing',
);
return allDbVendorsDone && !hasAssessingVendors;
@@ -171,12 +241,8 @@ export function VendorsTable({
const mergedVendors = useMemo(() => {
const dbVendorIds = new Set(vendors.map((v) => v.id));
- // Mark vendors in DB as "assessing" if they're not_assessed and onboarding is active
- // Don't mark as assessing if vendor is already assessed (resolved)
const vendorsWithStatus = vendors.map((vendor) => {
const metadataStatus = itemStatuses[vendor.id];
- // If vendor exists in DB but status is not_assessed and onboarding is active, it's being assessed
- // Only mark as assessing if status is not_assessed (not assessed)
if (vendor.status === 'not_assessed' && isActive && onboardingRunId && !metadataStatus) {
return { ...vendor, isAssessing: true };
}
@@ -185,269 +251,370 @@ export function VendorsTable({
const pendingVendors: VendorRow[] = itemsInfo
.filter((item) => {
- // Only show items that are pending/processing and not yet in DB
- const status = itemStatuses[item.id];
+ const itemStatus = itemStatuses[item.id];
return (
- (status === 'pending' || status === 'processing') &&
+ (itemStatus === 'pending' || itemStatus === 'processing') &&
!dbVendorIds.has(item.id) &&
!item.id.startsWith('temp_')
);
})
- .map((item) => {
- // Create a placeholder vendor row for pending items
- const status = itemStatuses[item.id];
- return {
- id: item.id,
- name: item.name,
- description: 'Being researched and created by AI...',
- category: 'other' as const,
- status: 'not_assessed' as const,
- inherentProbability: 'very_unlikely' as const,
- inherentImpact: 'insignificant' as const,
- residualProbability: 'very_unlikely' as const,
- residualImpact: 'insignificant' as const,
- website: null,
- organizationId: orgId,
- assigneeId: null,
- assignee: null,
- createdAt: new Date(),
- updatedAt: new Date(),
- isPending: true,
- } as VendorRow;
- });
-
- // Also handle temp IDs (vendors being created)
+ .map((item) => ({
+ id: item.id,
+ name: item.name,
+ description: 'Being researched and created by AI...',
+ category: 'other' as const,
+ status: 'not_assessed' as const,
+ inherentProbability: 'very_unlikely' as const,
+ inherentImpact: 'insignificant' as const,
+ residualProbability: 'very_unlikely' as const,
+ residualImpact: 'insignificant' as const,
+ website: null,
+ organizationId: orgId,
+ assigneeId: null,
+ assignee: null,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ isPending: true,
+ }));
+
const tempVendors: VendorRow[] = itemsInfo
.filter((item) => item.id.startsWith('temp_'))
- .map((item) => {
- const status = itemStatuses[item.id];
- return {
- id: item.id,
- name: item.name,
- description: 'Being researched and created by AI...',
- category: 'other' as const,
- status: 'not_assessed' as const,
- inherentProbability: 'very_unlikely' as const,
- inherentImpact: 'insignificant' as const,
- residualProbability: 'very_unlikely' as const,
- residualImpact: 'insignificant' as const,
- website: null,
- organizationId: orgId,
- assigneeId: null,
- assignee: null,
- createdAt: new Date(),
- updatedAt: new Date(),
- isPending: true,
- } as VendorRow;
- });
+ .map((item) => ({
+ id: item.id,
+ name: item.name,
+ description: 'Being researched and created by AI...',
+ category: 'other' as const,
+ status: 'not_assessed' as const,
+ inherentProbability: 'very_unlikely' as const,
+ inherentImpact: 'insignificant' as const,
+ residualProbability: 'very_unlikely' as const,
+ residualImpact: 'insignificant' as const,
+ website: null,
+ organizationId: orgId,
+ assigneeId: null,
+ assignee: null,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ isPending: true,
+ }));
return [...vendorsWithStatus, ...pendingVendors, ...tempVendors];
}, [vendors, itemsInfo, itemStatuses, orgId, isActive, onboardingRunId]);
- const dedupedVendors = useMemo(() => {
- // SAFE deduplication strategy:
- // 1. Show ALL real DB vendors (no deduplication) - user data must not be hidden
- // 2. Hide placeholders if a real vendor with same normalized name exists
- // 3. Deduplicate placeholders against each other (show only one per name)
-
- // Normalize vendor name for deduplication - strips parenthetical suffixes
- const normalizeVendorName = (name: string): string => {
- return name
- .toLowerCase()
- .replace(/\s*\([^)]*\)\s*$/, '') // Remove trailing parenthetical suffixes
- .trim();
- };
-
- // Separate real DB vendors from placeholders
- const realVendors = mergedVendors.filter((v) => !v.isPending);
- const placeholders = mergedVendors.filter((v) => v.isPending);
+ // Client-side filtering and sorting
+ const filteredAndSortedVendors = useMemo(() => {
+ let result = [...mergedVendors];
- // Build a set of normalized names from real vendors
- const realVendorNames = new Set(realVendors.map((v) => normalizeVendorName(v.name)));
-
- // Deduplicate placeholders: keep only one per name, and only if no real vendor exists
- const placeholderMap = new Map();
- placeholders.forEach((placeholder) => {
- const nameKey = normalizeVendorName(placeholder.name);
+ // Filter by search query
+ if (searchQuery) {
+ const query = searchQuery.toLowerCase();
+ result = result.filter((vendor) => vendor.name.toLowerCase().includes(query));
+ }
- // Skip if a real vendor with this name already exists
- if (realVendorNames.has(nameKey)) {
- return;
- }
+ // Sort
+ result.sort((a, b) => {
+ const aValue = sort.id === 'name' ? a.name : a.updatedAt;
+ const bValue = sort.id === 'name' ? b.name : b.updatedAt;
- // Keep the first placeholder for each name (or replace if needed)
- const existing = placeholderMap.get(nameKey);
- if (!existing) {
- placeholderMap.set(nameKey, placeholder);
+ if (sort.id === 'name') {
+ const comparison = (aValue as string).localeCompare(bValue as string);
+ return sort.desc ? -comparison : comparison;
}
- // If multiple placeholders with same name, keep the first one (no ranking needed)
+ const comparison =
+ new Date(aValue as Date).getTime() - new Date(bValue as Date).getTime();
+ return sort.desc ? -comparison : comparison;
});
- // Return all real vendors + deduplicated placeholders
- return [...realVendors, ...Array.from(placeholderMap.values())];
- }, [mergedVendors]);
-
- const columns = useMemo[]>(() => getColumns(orgId), [orgId]);
-
- const { table } = useDataTable({
- data: dedupedVendors,
- columns,
- pageCount,
- getRowId: (row) => row.id,
- initialState: {
- pagination: {
- pageSize: 50,
- pageIndex: 0,
- },
- sorting: [{ id: 'name', desc: false }],
- columnPinning: { right: ['delete-vendor'] },
- },
- shallow: false,
- clearOnDefault: true,
- });
-
- const getRowProps = useMemo(
- () => (vendor: VendorRow) => {
- const status = itemStatuses[vendor.id] || (vendor.isPending ? 'pending' : undefined);
- const isAssessing = vendor.isAssessing || status === 'assessing';
- const isBlocked =
- (status &&
- ACTIVE_STATUSES.includes(status as 'pending' | 'processing' | 'created' | 'assessing')) ||
- isAssessing;
-
- if (!isBlocked) {
- return {};
- }
-
- return {
- disabled: true,
- className:
- 'relative bg-muted/40 opacity-70 pointer-events-none after:absolute after:inset-0 after:bg-background/40 after:content-[""] after:animate-pulse',
- };
- },
- [itemStatuses],
- );
+ return result;
+ }, [mergedVendors, searchQuery, sort]);
- // Calculate actual assessment progress (using deduplicated counts to match the table)
+ // Calculate assessment progress
const assessmentProgress = useMemo(() => {
if (!progress || !itemsInfo.length) {
return null;
}
- // Normalize vendor name for deduplication (same as dedupedVendors)
- const normalizeVendorName = (name: string): string => {
- return name
- .toLowerCase()
- .replace(/\s*\([^)]*\)\s*$/, '')
- .trim();
- };
-
- // Build a map of unique vendor names with their best status
- // This mirrors the deduplication logic used for the table
- const uniqueVendorStatuses = new Map();
-
- // First, add all vendors from metadata
- itemsInfo.forEach((item) => {
- const nameKey = normalizeVendorName(item.name);
- const metadataStatus = itemStatuses[item.id];
- const isCompleted = metadataStatus === 'completed';
-
- const existing = uniqueVendorStatuses.get(nameKey);
- if (!existing || (isCompleted && !existing.isCompleted)) {
- uniqueVendorStatuses.set(nameKey, { isCompleted });
- }
- });
-
- // Then, update with DB vendor statuses (which may be more accurate)
- vendors.forEach((vendor) => {
- const nameKey = normalizeVendorName(vendor.name);
+ const completedCount = vendors.filter((vendor) => {
const metadataStatus = itemStatuses[vendor.id];
- const isCompleted = metadataStatus === 'completed' || vendor.status === 'assessed';
-
- const existing = uniqueVendorStatuses.get(nameKey);
- if (!existing || (isCompleted && !existing.isCompleted)) {
- uniqueVendorStatuses.set(nameKey, { isCompleted });
- }
- });
+ return metadataStatus === 'completed' || vendor.status === 'assessed';
+ }).length;
+
+ const completedInMetadata = Object.values(itemStatuses).filter(
+ (s) => s === 'completed',
+ ).length;
- const total = uniqueVendorStatuses.size;
- const completed = Array.from(uniqueVendorStatuses.values()).filter((v) => v.isCompleted).length;
+ const total = Math.max(progress.total, itemsInfo.length, vendors.length);
+ const completed = Math.max(completedCount, completedInMetadata);
return { total, completed };
}, [progress, itemsInfo, vendors, itemStatuses]);
- const isEmpty = dedupedVendors.length === 0;
- // Show empty state if onboarding is active (even if progress metadata isn't set yet)
- const showEmptyState = isEmpty && onboardingRunId && isActive;
+ const isRowBlocked = (vendor: VendorRow) => {
+ const vendorStatus = itemStatuses[vendor.id] || (vendor.isPending ? 'pending' : undefined);
+ const isAssessing = vendor.isAssessing || vendorStatus === 'assessing';
+ return (
+ (vendorStatus &&
+ ACTIVE_STATUSES.includes(
+ vendorStatus as 'pending' | 'processing' | 'created' | 'assessing',
+ )) ||
+ isAssessing
+ );
+ };
- // Prevent flicker: if we're loading onboarding status and have a runId, render null
- // Once we know the status, show animation if empty and active, otherwise show table
- if (onboardingRunId && isLoading) {
- return null;
- }
+ const handleRowClick = (vendorId: string) => {
+ router.push(`/${orgId}/vendors/${vendorId}`);
+ };
+
+ const handleSort = (columnId: 'name' | 'updatedAt') => {
+ if (sort.id === columnId) {
+ setSort({ id: columnId, desc: !sort.desc });
+ } else {
+ setSort({ id: columnId, desc: false });
+ }
+ };
+
+ const getSortIcon = (columnId: 'name' | 'updatedAt') => {
+ if (sort.id !== columnId) {
+ return ;
+ }
+ return sort.desc ? (
+
+ ) : (
+
+ );
+ };
+
+ const handleDeleteClick = (vendor: VendorRow) => {
+ setVendorToDelete(vendor);
+ setDeleteDialogOpen(true);
+ };
+
+ const handleConfirmDelete = async () => {
+ if (!vendorToDelete) return;
+
+ setIsDeleting(true);
+ try {
+ const result = await deleteVendor({ vendorId: vendorToDelete.id });
+ if (result?.data?.success) {
+ toast.success('Vendor deleted successfully');
+ setDeleteDialogOpen(false);
+ setVendorToDelete(null);
+ } else {
+ const errorMsg =
+ typeof result?.data?.error === 'string'
+ ? result.data.error
+ : 'Failed to delete vendor';
+ toast.error(errorMsg);
+ }
+ } catch {
+ toast.error('Failed to delete vendor');
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
+ const isEmpty = mergedVendors.length === 0;
+ const showEmptyState = isEmpty && onboardingRunId && isActive;
+ const emptyTitle = searchQuery ? 'No vendors found' : 'No vendors yet';
+ const emptyDescription = searchQuery
+ ? 'Try adjusting your search.'
+ : 'Create your first vendor to get started.';
- // Show loading animation instead of table when empty and onboarding is active
if (showEmptyState) {
return (
- <>
-
-
- >
+
);
}
return (
- <>
-
- row.id}
- rowClickBasePath={`/${orgId}/vendors`}
- getRowProps={getRowProps}
- >
- <>
-
- {isActive && !allVendorsDoneAssessing && (
-
-
-
-
-
-
- {assessmentProgress
- ? assessmentProgress.completed === 0
- ? 'Researching and creating vendors'
- : assessmentProgress.completed < assessmentProgress.total
- ? 'Assessing vendors and generating risk assessments'
- : 'Assessing vendors and generating risk assessments'
- : progress
- ? progress.completed === 0
- ? 'Researching and creating vendors'
- : 'Assessing vendors and generating risk assessments'
- : 'Researching and creating vendors'}
-
-
- {assessmentProgress
- ? assessmentProgress.completed === 0
- ? 'AI is analyzing your organization...'
- : `${assessmentProgress.completed}/${assessmentProgress.total} vendors assessed`
- : progress
- ? progress.completed === 0
- ? 'AI is analyzing your organization...'
- : `${progress.completed}/${progress.total} vendors created`
- : 'AI is analyzing your organization...'}
-
-
-
- )}
- >
-
-
-
- >
+
+
+ {/* Search Bar */}
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+ {/* Onboarding Progress Banner */}
+ {isActive && !allVendorsDoneAssessing && (
+
+
+
+
+
+
+ {assessmentProgress
+ ? assessmentProgress.completed === 0
+ ? 'Researching and creating vendors'
+ : 'Assessing vendors and generating risk assessments'
+ : progress
+ ? progress.completed === 0
+ ? 'Researching and creating vendors'
+ : 'Assessing vendors and generating risk assessments'
+ : 'Researching and creating vendors'}
+
+
+ {assessmentProgress
+ ? assessmentProgress.completed === 0
+ ? 'AI is analyzing your organization...'
+ : `${assessmentProgress.completed}/${assessmentProgress.total} vendors assessed`
+ : progress
+ ? progress.completed === 0
+ ? 'AI is analyzing your organization...'
+ : `${progress.completed}/${progress.total} vendors created`
+ : 'AI is analyzing your organization...'}
+
+
+
+ )}
+
+ {/* Table */}
+ {isEmpty ? (
+
+
+ {emptyTitle}
+ {emptyDescription}
+
+
+ ) : (
+
+
+
+
+
+
+ STATUS
+ CATEGORY
+ OWNER
+ ACTIONS
+
+
+
+ {filteredAndSortedVendors.map((vendor) => {
+ const blocked = isRowBlocked(vendor);
+ return (
+ !blocked && handleRowClick(vendor.id)}
+ style={{ cursor: blocked ? 'default' : 'pointer' }}
+ data-state={blocked ? 'disabled' : undefined}
+ >
+
+
+
+
+
+
+
+
+ {CATEGORY_MAP[vendor.category] || vendor.category}
+
+
+
+ {vendor.assignee ? (
+
+
+
+
+ {vendor.assignee.user?.name?.charAt(0) ||
+ vendor.assignee.user?.email?.charAt(0).toUpperCase() ||
+ '?'}
+
+
+
+ {vendor.assignee.user?.name || vendor.assignee.user?.email || 'Unknown'}
+
+
+ ) : (
+
+
+
+
+
+ None
+
+
+ )}
+
+
+
+
+ e.stopPropagation()}
+ >
+
+
+
+ handleRowClick(vendor.id)}>
+
+ View Details
+
+
+ handleDeleteClick(vendor)}
+ >
+
+ Delete
+
+
+
+
+
+
+ );
+ })}
+
+
+ )}
+
+ {/* Delete Confirmation Dialog */}
+
+
+
+ Delete Vendor
+
+ Are you sure you want to delete "{vendorToDelete?.name}"? This action cannot be
+ undone.
+
+
+
+ Cancel
+
+ {isDeleting ? 'Deleting...' : 'Delete'}
+
+
+
+
+
+
);
}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/loading.tsx b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/loading.tsx
index 4f38f9a92..fe5a41076 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/loading.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/loading.tsx
@@ -1,9 +1,5 @@
-import Loader from '@/components/ui/loader';
+import { PageHeader, PageLayout } from '@trycompai/design-system';
export default function Loading() {
- return (
-
-
-
- );
+ return } />;
}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/page.tsx b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/page.tsx
index 0027f90e2..1d9ab2c2b 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/page.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/page.tsx
@@ -1,8 +1,7 @@
import { AppOnboarding } from '@/components/app-onboarding';
-import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
import type { SearchParams } from '@/types';
import { db } from '@db';
-import type { Metadata } from 'next';
+import { PageHeader, PageLayout } from '@trycompai/design-system';
import { CreateVendorSheet } from '../components/create-vendor-sheet';
import { VendorsTable } from './components/VendorsTable';
import { getAssignees, getVendors } from './data/queries';
@@ -48,7 +47,14 @@ export default async function Page({
// Show AppOnboarding only if empty, default view, AND onboarding is not active
if (isEmpty && isDefault && !isOnboardingActive) {
return (
-
+
);
}
return (
- }
+ />
+ }
>
-
+
);
}
-
-export async function generateMetadata(): Promise {
- return {
- title: 'Vendors',
- };
-}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorDetailTabs.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorDetailTabs.tsx
new file mode 100644
index 000000000..c77af10ec
--- /dev/null
+++ b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorDetailTabs.tsx
@@ -0,0 +1,110 @@
+'use client';
+
+import { VendorRiskAssessmentView } from '@/components/vendor-risk-assessment/VendorRiskAssessmentView';
+import type { Member, User, Vendor } from '@db';
+import type { Prisma } from '@prisma/client';
+import {
+ PageHeader,
+ PageLayout,
+ Stack,
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+ Text,
+} from '@trycompai/design-system';
+import { VendorActions } from './VendorActions';
+import { VendorPageClient } from './VendorPageClient';
+
+// Vendor with risk assessment data merged from GlobalVendors
+type VendorWithRiskAssessment = Vendor & {
+ assignee: { user: User | null } | null;
+ riskAssessmentData?: Prisma.InputJsonValue | null;
+ riskAssessmentVersion?: string | null;
+ riskAssessmentUpdatedAt?: Date | null;
+};
+
+interface VendorDetailTabsProps {
+ vendorId: string;
+ orgId: string;
+ vendor: VendorWithRiskAssessment;
+ assignees: (Member & { user: User })[];
+ isViewingTask: boolean;
+}
+
+export function VendorDetailTabs({
+ vendorId,
+ orgId,
+ vendor,
+ assignees,
+ isViewingTask,
+}: VendorDetailTabsProps) {
+ const breadcrumbs = [
+ { label: 'Vendors', href: `/${orgId}/vendors` },
+ {
+ label: vendor?.name ?? '',
+ href: isViewingTask ? `/${orgId}/vendors/${vendorId}` : undefined,
+ isCurrent: !isViewingTask,
+ },
+ ];
+
+ const riskAssessmentData = vendor.riskAssessmentData;
+ const riskAssessmentUpdatedAt = vendor.riskAssessmentUpdatedAt ?? null;
+
+ return (
+
+ }
+ tabs={
+ !isViewingTask && (
+
+ Overview
+ Risk Assessment
+
+ )
+ }
+ />
+ }
+ >
+
+
+
+
+
+
+ {riskAssessmentData ? (
+
+ ) : (
+
+
+ {vendor.status === 'in_progress'
+ ? 'Risk assessment is being generated. Please check back soon.'
+ : 'No risk assessment found yet.'}
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorPageClient.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorPageClient.tsx
index 60ba8c80a..fdd808b5c 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorPageClient.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorPageClient.tsx
@@ -11,7 +11,6 @@ import { SecondaryFields } from './secondary-fields/secondary-fields';
import { VendorHeader } from './VendorHeader';
import { VendorInherentRiskChart } from './VendorInherentRiskChart';
import { VendorResidualRiskChart } from './VendorResidualRiskChart';
-import { VendorTabs } from './VendorTabs';
// Vendor with risk assessment data merged from GlobalVendors
type VendorWithRiskAssessment = Vendor & {
@@ -83,7 +82,6 @@ export function VendorPageClient({
return (
<>
{!isViewingTask && }
- {!isViewingTask && }
{!isViewingTask && (
<>
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorTabs.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorTabs.tsx
deleted file mode 100644
index 500aeb7c8..000000000
--- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/components/VendorTabs.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { SecondaryMenu } from '@comp/ui/secondary-menu';
-
-interface VendorTabsProps {
- vendorId: string;
- orgId: string;
-}
-
-export function VendorTabs({ vendorId, orgId }: VendorTabsProps) {
- const items = [
- {
- path: `/${orgId}/vendors/${vendorId}`,
- label: 'Overview',
- },
- {
- path: `/${orgId}/vendors/${vendorId}/review`,
- label: 'Risk Assessment',
- },
- ];
-
- return
;
-}
-
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/layout.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/layout.tsx
deleted file mode 100644
index 49ea4bdf6..000000000
--- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/layout.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-interface VendorLayoutProps {
- children: React.ReactNode;
-}
-
-export default function VendorLayout({ children }: VendorLayoutProps) {
- return <>{children}>;
-}
-
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/loading.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/loading.tsx
index 8071fa5db..3123e106b 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/loading.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/loading.tsx
@@ -1,74 +1,5 @@
-import { Skeleton } from '@comp/ui/skeleton';
+import { PageHeader, PageLayout } from '@trycompai/design-system';
export default function Loading() {
- return (
-
- {/* Vendor header skeleton */}
-
-
- {/* Tabs skeleton */}
-
-
- {/* Content skeleton */}
-
- {/* Secondary Fields skeleton */}
-
-
- {/* Charts skeleton */}
-
-
- {/* Tasks skeleton */}
-
-
-
- );
+ return
} />;
}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/page.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/page.tsx
index 857ccfceb..2a567db28 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/page.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/page.tsx
@@ -1,4 +1,3 @@
-import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
import { auth } from '@/utils/auth';
import { extractDomain } from '@/utils/normalize-website';
import { db } from '@db';
@@ -6,8 +5,7 @@ import type { Metadata } from 'next';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { cache } from 'react';
-import { VendorActions } from './components/VendorActions';
-import { VendorPageClient } from './components/VendorPageClient';
+import { VendorDetailTabs } from './components/VendorDetailTabs';
interface PageProps {
params: Promise<{ vendorId: string; locale: string; orgId: string }>;
@@ -19,12 +17,12 @@ interface PageProps {
/**
* Vendor detail page - server component
* Fetches initial data server-side for fast first render
- * Passes data to VendorPageClient which uses SWR for real-time updates
+ * Passes data to VendorDetailTabs which handles both Overview and Risk Assessment tabs
*/
export default async function VendorPage({ params, searchParams }: PageProps) {
const { vendorId, orgId } = await params;
const { taskItemId } = (await searchParams) ?? {};
-
+
// Fetch data in parallel for faster loading
const [vendorData, assignees] = await Promise.all([
getVendor({ vendorId, organizationId: orgId }),
@@ -39,26 +37,13 @@ export default async function VendorPage({ params, searchParams }: PageProps) {
const isViewingTask = Boolean(taskItemId);
return (
-
}
- >
-
-
+
);
}
@@ -103,12 +88,9 @@ const getVendor = cache(async (params: { vendorId: string; organizationId: strin
riskAssessmentVersion: true,
riskAssessmentUpdatedAt: true,
},
- orderBy: [
- { riskAssessmentUpdatedAt: 'desc' },
- { createdAt: 'desc' },
- ],
+ orderBy: [{ riskAssessmentUpdatedAt: 'desc' }, { createdAt: 'desc' }],
});
-
+
// Prefer record WITH risk assessment data (most recent)
globalVendor = duplicates.find((gv) => gv.riskAssessmentData !== null) ?? duplicates[0] ?? null;
}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/loading.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/loading.tsx
index af5196b16..d7dbc25c3 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/loading.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/loading.tsx
@@ -1,97 +1,7 @@
-import { Skeleton } from '@comp/ui/skeleton';
+import { redirect } from 'next/navigation';
+// This route redirects to the main vendor page
export default function Loading() {
- return (
-
- {/* Vendor header skeleton */}
-
-
- {/* Tabs skeleton */}
-
-
- {/* Risk Assessment skeleton */}
-
- {/* Header skeleton */}
-
-
-
-
-
- {/* Main content grid skeleton */}
-
- {/* Left column - 2/3 width */}
-
- {/* Security Assessment card */}
-
-
- {/* Timeline card */}
-
-
-
- {/* Right column - 1/3 width */}
-
- {/* Useful Links card */}
-
-
- {/* Certifications card */}
-
-
- {/* Vendor Details card */}
-
-
-
-
-
- );
+ return null;
}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/page.tsx b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/page.tsx
index 4dfcb706a..7da537230 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/page.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/review/page.tsx
@@ -1,157 +1,12 @@
-import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
-import type { VendorResponse } from '@/hooks/use-vendors';
-import { auth } from '@/utils/auth';
-import { extractDomain } from '@/utils/normalize-website';
-import { db } from '@db';
-import type { Metadata } from 'next';
-import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
-import { cache } from 'react';
-import { VendorActions } from '../components/VendorActions';
-import { VendorHeader } from '../components/VendorHeader';
-import { VendorTabs } from '../components/VendorTabs';
-import { VendorReviewClient } from './components/VendorReviewClient';
interface ReviewPageProps {
- params: Promise<{ vendorId: string; locale: string; orgId: string }>;
- searchParams?: Promise<{
- taskItemId?: string;
- }>;
+ params: Promise<{ vendorId: string; orgId: string }>;
}
-export default async function ReviewPage({ params, searchParams }: ReviewPageProps) {
+// Redirect to main vendor page - review tab is now part of the consolidated page
+export default async function ReviewPage({ params }: ReviewPageProps) {
const { vendorId, orgId } = await params;
- const { taskItemId } = (await searchParams) ?? {};
-
- const vendorResult = await getVendor({ vendorId, organizationId: orgId });
-
- if (!vendorResult || !vendorResult.vendor || !vendorResult.vendorForClient) {
- redirect('/');
- }
-
- // Hide tabs when viewing a task in focus mode
- const isViewingTask = Boolean(taskItemId);
- const { vendor, vendorForClient } = vendorResult;
-
- return (
-
}
- >
- {!isViewingTask &&
}
- {!isViewingTask &&
}
-
-
-
-
- );
-}
-
-const getVendor = cache(async (params: { vendorId: string; organizationId: string }) => {
- const { vendorId, organizationId } = params;
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- if (!session?.user?.id) {
- return null;
- }
-
- const vendor = await db.vendor.findUnique({
- where: {
- id: vendorId,
- organizationId,
- },
- select: {
- id: true,
- name: true,
- description: true,
- website: true,
- status: true,
- updatedAt: true,
- createdAt: true,
- category: true,
- inherentProbability: true,
- inherentImpact: true,
- residualProbability: true,
- residualImpact: true,
- organizationId: true,
- assigneeId: true,
- },
- });
-
- if (!vendor) {
- return null;
- }
-
- // Fetch risk assessment from GlobalVendors if vendor has a website
- // Find ALL duplicates and prefer the one WITH risk assessment data (most recent)
- const domain = extractDomain(vendor.website ?? null);
- let globalVendor = null;
- if (domain) {
- const duplicates = await db.globalVendors.findMany({
- where: {
- website: {
- contains: domain,
- },
- },
- select: {
- website: true,
- riskAssessmentData: true,
- riskAssessmentVersion: true,
- riskAssessmentUpdatedAt: true,
- },
- orderBy: [
- { riskAssessmentUpdatedAt: 'desc' },
- { createdAt: 'desc' },
- ],
- });
-
- // Prefer record WITH risk assessment data (most recent)
- globalVendor = duplicates.find((gv) => gv.riskAssessmentData !== null) ?? duplicates[0] ?? null;
- }
-
- // Return vendor with Date objects for VendorHeader (server component compatible)
- const vendorWithRiskAssessment = {
- ...vendor,
- riskAssessmentData: globalVendor?.riskAssessmentData ?? null,
- riskAssessmentVersion: globalVendor?.riskAssessmentVersion ?? null,
- riskAssessmentUpdatedAt: globalVendor?.riskAssessmentUpdatedAt ?? null,
- };
-
- // Serialize dates to strings for VendorReviewClient (client component)
- const vendorForClient: VendorResponse = {
- ...vendor,
- description: vendor.description ?? '',
- createdAt: vendor.createdAt.toISOString(),
- updatedAt: vendor.updatedAt.toISOString(),
- riskAssessmentData: globalVendor?.riskAssessmentData ?? null,
- riskAssessmentVersion: globalVendor?.riskAssessmentVersion ?? null,
- riskAssessmentUpdatedAt: globalVendor?.riskAssessmentUpdatedAt?.toISOString() ?? null,
- };
-
- return {
- vendor: vendorWithRiskAssessment,
- vendorForClient,
- };
-});
-
-export async function generateMetadata(): Promise
{
- return {
- title: 'Vendor Risk Assessment',
- };
+ redirect(`/${orgId}/vendors/${vendorId}`);
}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/components/VendorNameAutocompleteField.tsx b/apps/app/src/app/(app)/[orgId]/vendors/components/VendorNameAutocompleteField.tsx
index f6d8405af..fe1ffb848 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/components/VendorNameAutocompleteField.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/components/VendorNameAutocompleteField.tsx
@@ -5,7 +5,7 @@ import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@comp/
import { Input } from '@comp/ui/input';
import type { GlobalVendors } from '@db';
import { useAction } from 'next-safe-action/hooks';
-import { useEffect, useMemo, useRef, useState } from 'react';
+import { useMemo, useState } from 'react';
import type { UseFormReturn } from 'react-hook-form';
import { searchGlobalVendorsAction } from '../actions/search-global-vendors-action';
import type { CreateVendorFormValues } from './create-vendor-form-schema';
@@ -29,18 +29,14 @@ const getVendorKey = (vendor: GlobalVendors): string => {
type Props = {
form: UseFormReturn;
- isSheetOpen: boolean;
};
-export function VendorNameAutocompleteField({ form, isSheetOpen }: Props) {
+export function VendorNameAutocompleteField({ form }: Props) {
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
const [popoverOpen, setPopoverOpen] = useState(false);
- // Used to avoid resetting on initial mount.
- const hasOpenedOnceRef = useRef(false);
-
const searchVendors = useAction(searchGlobalVendorsAction, {
onExecute: () => setIsSearching(true),
onSuccess: (result) => {
@@ -65,21 +61,6 @@ export function VendorNameAutocompleteField({ form, isSheetOpen }: Props) {
}
}, 300);
- // Reset autocomplete state when the sheet closes.
- useEffect(() => {
- if (isSheetOpen) {
- hasOpenedOnceRef.current = true;
- return;
- }
-
- if (!hasOpenedOnceRef.current) return;
-
- setSearchQuery('');
- setSearchResults([]);
- setIsSearching(false);
- setPopoverOpen(false);
- }, [isSheetOpen]);
-
const deduplicatedSearchResults = useMemo(() => {
if (searchResults.length === 0) return [];
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/components/create-vendor-form.tsx b/apps/app/src/app/(app)/[orgId]/vendors/components/create-vendor-form.tsx
index 2ecf0ac42..193a3b3a3 100644
--- a/apps/app/src/app/(app)/[orgId]/vendors/components/create-vendor-form.tsx
+++ b/apps/app/src/app/(app)/[orgId]/vendors/components/create-vendor-form.tsx
@@ -12,8 +12,7 @@ import { type Member, type User, type Vendor, VendorCategory, VendorStatus } fro
import { zodResolver } from '@hookform/resolvers/zod';
import { ArrowRightIcon } from 'lucide-react';
import { useAction } from 'next-safe-action/hooks';
-import { useQueryState } from 'nuqs';
-import { useEffect, useRef } from 'react';
+import { useRef } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { useSWRConfig } from 'swr';
@@ -24,14 +23,14 @@ import { createVendorSchema, type CreateVendorFormValues } from './create-vendor
export function CreateVendorForm({
assignees,
organizationId,
+ onSuccess,
}: {
assignees: (Member & { user: User })[];
organizationId: string;
+ onSuccess?: () => void;
}) {
const { mutate } = useSWRConfig();
- const [createVendorSheet, setCreateVendorSheet] = useQueryState('createVendorSheet');
- const isMountedRef = useRef(false);
const pendingWebsiteRef = useRef(null);
const createVendor = useAction(createVendorAction, {
@@ -66,10 +65,8 @@ export function CreateVendorForm({
// Show success toast
toast.success('Vendor created successfully');
- // Close sheet last - use setTimeout to ensure it happens after all state updates
- setTimeout(() => {
- setCreateVendorSheet(null);
- }, 0);
+ // Close sheet
+ onSuccess?.();
},
onError: (error) => {
// Handle thrown errors (shouldn't happen with our try-catch, but keep as fallback)
@@ -93,25 +90,6 @@ export function CreateVendorForm({
mode: 'onChange',
});
- // Reset form state when sheet closes
- useEffect(() => {
- const isOpen = Boolean(createVendorSheet);
-
- if (!isOpen && isMountedRef.current) {
- // Sheet was closed - reset all state
- form.reset({
- name: '',
- website: '',
- description: '',
- category: VendorCategory.cloud,
- status: VendorStatus.not_assessed,
- });
- } else if (isOpen) {
- // Sheet opened - mark as mounted
- isMountedRef.current = true;
- }
- }, [createVendorSheet, form]);
-
const onSubmit = async (data: CreateVendorFormValues) => {
// Prevent double-submits (also disabled via button state)
if (createVendor.status === 'executing') return;
@@ -126,7 +104,7 @@ export function CreateVendorForm({
{/* p-1 prevents focus ring (box-shadow) being clipped by overflow containers */}
-
+
{
- setOpen(open ? 'true' : null);
- };
+ const handleSuccess = useCallback(() => {
+ setIsOpen(false);
+ }, []);
+
+ const trigger = (
+ } onClick={() => setIsOpen(true)}>
+ Add Vendor
+
+ );
if (isDesktop) {
return (
-
-
-
- {'Create Vendor'}
-
-
-
-
-
-
-
-
+ <>
+ {trigger}
+
+
+
+ Create Vendor
+
+
+
+
+
+
+ >
);
}
return (
-
- {'Create Vendor'}
-
-
-
-
+ <>
+ {trigger}
+
+
+
+ Create Vendor
+
+
+
+
+
+
+ >
);
}
diff --git a/apps/app/src/app/(app)/[orgId]/vendors/layout.tsx b/apps/app/src/app/(app)/[orgId]/vendors/layout.tsx
deleted file mode 100644
index 983c329e5..000000000
--- a/apps/app/src/app/(app)/[orgId]/vendors/layout.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default async function Layout({ children }: { children: React.ReactNode }) {
- return {children}
;
-}
diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx
index 09a28980a..3917ea35e 100644
--- a/apps/app/src/app/layout.tsx
+++ b/apps/app/src/app/layout.tsx
@@ -1,5 +1,4 @@
-import '@/styles/globals.css';
-import '@comp/ui/globals.css';
+import '@trycompai/design-system/globals.css';
import { LinkedInInsight } from '@/components/tracking/LinkedInInsight';
import { env } from '@/env.mjs';
diff --git a/apps/app/src/components/ai/chat-button.tsx b/apps/app/src/components/ai/chat-button.tsx
index fcbc2a6e2..60ef70b84 100644
--- a/apps/app/src/components/ai/chat-button.tsx
+++ b/apps/app/src/components/ai/chat-button.tsx
@@ -13,9 +13,6 @@ export function AssistantButton() {
return (
);
}
diff --git a/apps/app/src/components/comments/CommentItem.tsx b/apps/app/src/components/comments/CommentItem.tsx
index 1689fff49..0087f4347 100644
--- a/apps/app/src/components/comments/CommentItem.tsx
+++ b/apps/app/src/components/comments/CommentItem.tsx
@@ -2,32 +2,29 @@
import { useApi } from '@/hooks/use-api';
import { useCommentActions } from '@/hooks/use-comments-api';
-import { Avatar, AvatarFallback, AvatarImage } from '@comp/ui/avatar';
+import { useOrganizationMembers } from '@/hooks/use-organization-members';
import { Button } from '@comp/ui/button';
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from '@comp/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@comp/ui/dropdown-menu';
-import { CommentRichTextField } from './CommentRichTextField';
-import { useOrganizationMembers } from '@/hooks/use-organization-members';
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@comp/ui/tooltip';
import type { JSONContent } from '@tiptap/react';
-import { useMemo } from 'react';
import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from '@comp/ui/tooltip';
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from '@trycompai/design-system';
import {
AlertTriangle,
FileIcon,
@@ -38,11 +35,12 @@ import {
Trash2,
} from 'lucide-react';
import type React from 'react';
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import { toast } from 'sonner';
import { formatRelativeTime } from '../../app/(app)/[orgId]/tasks/[taskId]/components/commentUtils';
-import type { CommentWithAuthor } from './Comments';
import { CommentContentView } from './CommentContentView';
+import { CommentRichTextField } from './CommentRichTextField';
+import type { CommentWithAuthor } from './Comments';
// Helper function to generate gravatar URL
function getGravatarUrl(email: string | null | undefined, size = 64): string {
@@ -217,14 +215,12 @@ export function CommentItem({ comment, refreshComments }: CommentItemProps) {
<>
-
+
-
- {comment.author.name?.charAt(0).toUpperCase() ?? '?'}
-
+ {comment.author.name?.charAt(0).toUpperCase() ?? '?'}
{comment.author.deactivated && (
@@ -352,24 +348,26 @@ export function CommentItem({ comment, refreshComments }: CommentItemProps) {
{/* Delete confirmation dialog */}
-
+
+
+
+ Cancel
+
+ Delete
+
+
+
+
>
);
}
diff --git a/apps/app/src/components/comments/Comments.tsx b/apps/app/src/components/comments/Comments.tsx
index 900ec4ea4..8bdbee9e5 100644
--- a/apps/app/src/components/comments/Comments.tsx
+++ b/apps/app/src/components/comments/Comments.tsx
@@ -1,8 +1,8 @@
'use client';
import { useComments } from '@/hooks/use-comments-api';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@comp/ui/card';
import { CommentEntityType } from '@db';
+import { Section, Stack, Text } from '@trycompai/design-system';
import { useParams } from 'next/navigation';
import { CommentForm } from './CommentForm';
import { CommentList } from './CommentList';
@@ -22,7 +22,6 @@ export type CommentWithAuthor = {
name: string;
type: string;
createdAt: string;
- // downloadUrl removed - now generated on-demand only
}>;
createdAt: string;
};
@@ -39,8 +38,6 @@ interface CommentsProps {
title?: string;
/** Optional custom description */
description?: string;
- /** Whether to show as a card or just the content */
- variant?: 'card' | 'inline';
}
/**
@@ -52,12 +49,11 @@ interface CommentsProps {
*
*
* @example
- * // Custom title and inline variant
+ * // Custom title
*
*/
export const Comments = ({
@@ -66,7 +62,6 @@ export const Comments = ({
organizationId,
title = 'Comments',
description,
- variant = 'card',
}: CommentsProps) => {
const params = useParams();
const orgIdFromParams =
@@ -91,68 +86,45 @@ export const Comments = ({
// Extract comments from SWR response
const comments = commentsData?.data || [];
- // Generate default description if not provided
- const defaultDescription = description || `Leave a comment on this ${entityType}`;
-
- const content = (
-
-
+ return (
+
+
+
- {commentsLoading && (
-
- {/* Enhanced comment skeletons */}
- {[1, 2].map((i) => (
-
-
-
-
-
-
-
+ {commentsLoading && (
+
+ {/* Enhanced comment skeletons */}
+ {[1, 2].map((i) => (
+
-
- ))}
-
- )}
-
- {commentsError && (
-
Failed to load comments. Please try again.
- )}
-
- {!commentsLoading && !commentsError && (
-
- )}
-
- );
+ ))}
+
+ )}
- if (variant === 'inline') {
- return (
-
- {title && (
-
-
{title}
- {description &&
{description}
}
-
+ {commentsError && (
+
+ Failed to load comments. Please try again.
+
)}
- {content}
-
- );
- }
- return (
-
-
- {title}
- {defaultDescription}
-
- {content}
-
+ {!commentsLoading && !commentsError && (
+
+ )}
+
+
);
};
diff --git a/apps/app/src/components/forms/organization/delete-organization.tsx b/apps/app/src/components/forms/organization/delete-organization.tsx
index f10bc74bc..c8c78ddef 100644
--- a/apps/app/src/components/forms/organization/delete-organization.tsx
+++ b/apps/app/src/components/forms/organization/delete-organization.tsx
@@ -1,6 +1,9 @@
'use client';
import { deleteOrganizationAction } from '@/actions/organization/delete-organization-action';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@comp/ui/card';
+import { Input } from '@comp/ui/input';
+import { Label } from '@comp/ui/label';
import {
AlertDialog,
AlertDialogAction,
@@ -11,18 +14,8 @@ import {
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
-} from '@comp/ui/alert-dialog';
-import { Button } from '@comp/ui/button';
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from '@comp/ui/card';
-import { Input } from '@comp/ui/input';
-import { Label } from '@comp/ui/label';
+ Button,
+} from '@trycompai/design-system';
import { Loader2 } from 'lucide-react';
import { useAction } from 'next-safe-action/hooks';
import { redirect } from 'next/navigation';
@@ -55,63 +48,61 @@ export function DeleteOrganization({
return (
-
- {'Delete organization'}
-
-
-
- {
- 'Permanently remove your organization and all of its contents from the Comp AI platform. This action is not reversible - please continue with caution.'
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
- {'Are you absolutely sure?'}
-
+
+
+
{'Delete organization'}
+
+
{
- 'This action cannot be undone. This will permanently delete your organization and remove your data from our servers.'
+ 'Permanently remove your organization and all of its contents from the Comp AI platform. This action is not reversible - please continue with caution.'
}
-
-
+
+
+
+
+ {'Delete'}} />
+
+
+ {'Are you absolutely sure?'}
+
+ {
+ 'This action cannot be undone. This will permanently delete your organization and remove your data from our servers.'
+ }
+
+
-
-
- setValue(e.target.value)} />
-
+
+
+ setValue(e.target.value)}
+ />
+
-
- {'Cancel'}
-
- deleteOrganization.execute({
- id: organizationId,
- organizationId,
- })
- }
- disabled={value !== 'delete'}
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
- >
- {deleteOrganization.status === 'executing' ? (
-
- ) : null}
- {'Delete'}
-
-
-
-
-
+
+ {'Cancel'}
+
+ deleteOrganization.execute({
+ id: organizationId,
+ organizationId,
+ })
+ }
+ disabled={value !== 'delete'}
+ variant="destructive"
+ >
+ {deleteOrganization.status === 'executing' ? (
+
+ ) : null}
+ {'Delete'}
+
+
+
+
+
+
+
);
}
diff --git a/apps/app/src/components/forms/organization/update-organization-advanced-mode.tsx b/apps/app/src/components/forms/organization/update-organization-advanced-mode.tsx
index 6b823007e..a543a2531 100644
--- a/apps/app/src/components/forms/organization/update-organization-advanced-mode.tsx
+++ b/apps/app/src/components/forms/organization/update-organization-advanced-mode.tsx
@@ -11,8 +11,8 @@ import {
CardTitle,
} from '@comp/ui/card';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@comp/ui/form';
-import { Switch } from '@comp/ui/switch';
import { zodResolver } from '@hookform/resolvers/zod';
+import { Switch } from '@trycompai/design-system';
import { Loader2 } from 'lucide-react';
import { useAction } from 'next-safe-action/hooks';
import { useForm } from 'react-hook-form';
diff --git a/apps/app/src/components/forms/policies/create-new-policy.tsx b/apps/app/src/components/forms/policies/create-new-policy.tsx
index 87f910675..35e9f0c65 100644
--- a/apps/app/src/components/forms/policies/create-new-policy.tsx
+++ b/apps/app/src/components/forms/policies/create-new-policy.tsx
@@ -2,32 +2,41 @@
import { createPolicyAction } from '@/actions/policies/create-new-policy';
import { createPolicySchema, type CreatePolicySchema } from '@/actions/schema';
-import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@comp/ui/accordion';
-import { Button } from '@comp/ui/button';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@comp/ui/form';
-import { Input } from '@comp/ui/input';
-import { Textarea } from '@comp/ui/textarea';
import { zodResolver } from '@hookform/resolvers/zod';
-import { ArrowRightIcon } from 'lucide-react';
+import { Button, Input, Label, Stack, Text, Textarea } from '@trycompai/design-system';
+import { ArrowRight } from '@trycompai/design-system/icons';
import { useAction } from 'next-safe-action/hooks';
-import { useQueryState } from 'nuqs';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
export function CreateNewPolicyForm() {
- const [_, setCreatePolicySheet] = useQueryState('create-policy-sheet');
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+
+ const closeSheet = () => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.delete('create-policy-sheet');
+ const query = params.toString();
+ router.push(query ? `${pathname}?${query}` : pathname);
+ };
const createPolicy = useAction(createPolicyAction, {
onSuccess: () => {
toast.success('Policy successfully created');
- setCreatePolicySheet(null);
+ closeSheet();
},
onError: () => {
toast.error('Failed to create policy');
},
});
- const form = useForm
({
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
resolver: zodResolver(createPolicySchema),
defaultValues: {
title: '',
@@ -39,67 +48,46 @@ export function CreateNewPolicyForm() {
createPolicy.execute(data);
};
+ const isLoading = createPolicy.status === 'executing';
+
return (
-
-
+
);
}
diff --git a/apps/app/src/components/forms/risks/create-risk-form.tsx b/apps/app/src/components/forms/risks/create-risk-form.tsx
index 1c6c024ca..0bf2a1402 100644
--- a/apps/app/src/components/forms/risks/create-risk-form.tsx
+++ b/apps/app/src/components/forms/risks/create-risk-form.tsx
@@ -3,45 +3,57 @@
import { createRiskAction } from '@/actions/risk/create-risk-action';
import { createRiskSchema } from '@/actions/schema';
import { SelectAssignee } from '@/components/SelectAssignee';
-import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@comp/ui/accordion';
import { Button } from '@comp/ui/button';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@comp/ui/form';
-import { Input } from '@comp/ui/input';
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@comp/ui/select';
-import { Textarea } from '@comp/ui/textarea';
-import type { Member, RiskStatus, User } from '@db';
+import type { Member, User } from '@db';
import { Departments, RiskCategory } from '@db';
import { zodResolver } from '@hookform/resolvers/zod';
-import { ArrowRightIcon } from 'lucide-react';
+import {
+ Field,
+ FieldError,
+ FieldGroup,
+ FieldLabel,
+ HStack,
+ Input,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+ SheetFooter,
+ Textarea,
+} from '@trycompai/design-system';
+import { ArrowRight } from '@trycompai/design-system/icons';
import { useAction } from 'next-safe-action/hooks';
-import { useQueryState } from 'nuqs';
-import { useForm } from 'react-hook-form';
+import { Controller, useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { useSWRConfig } from 'swr';
import type { z } from 'zod';
-export function CreateRisk({ assignees }: { assignees: (Member & { user: User })[] }) {
- const { mutate } = useSWRConfig();
+interface CreateRiskProps {
+ assignees: (Member & { user: User })[];
+ onSuccess?: () => void;
+}
- const [_, setCreateRiskSheet] = useQueryState('create-risk-sheet');
+export function CreateRisk({ assignees, onSuccess }: CreateRiskProps) {
+ const { mutate } = useSWRConfig();
const createRisk = useAction(createRiskAction, {
- onSuccess: async () => {
+ onSuccess: () => {
toast.success('Risk created successfully');
- setCreateRiskSheet(null);
- // Invalidate all risks SWR caches (any key starting with 'risks')
- mutate(
- (key) => Array.isArray(key) && key[0] === 'risks',
- undefined,
- { revalidate: true },
- );
+ onSuccess?.();
+ mutate((key) => Array.isArray(key) && key[0] === 'risks', undefined, { revalidate: true });
},
onError: () => {
toast.error('Failed to create risk');
},
});
- const form = useForm>({
+ const {
+ register,
+ handleSubmit,
+ control,
+ formState: { errors },
+ } = useForm>({
resolver: zodResolver(createRiskSchema),
defaultValues: {
title: '',
@@ -57,153 +69,110 @@ export function CreateRisk({ assignees }: { assignees: (Member & { user: User })
};
return (
-
-
+
+
+
+
+
+
);
}
diff --git a/apps/app/src/components/hot-keys.tsx b/apps/app/src/components/hot-keys.tsx
index d80f1ace2..fb2e60f16 100644
--- a/apps/app/src/components/hot-keys.tsx
+++ b/apps/app/src/components/hot-keys.tsx
@@ -6,11 +6,6 @@ import { useHotkeys } from 'react-hotkeys-hook';
export function HotKeys() {
const router = useRouter();
- const [, setAssistantOpen] = useQueryState('assistant', {
- history: 'push',
- parse: (value) => value === 'true',
- serialize: (value) => value.toString(),
- });
const [showOrganizationSwitcher, setShowOrganizationSwitcher] = useQueryState(
'showOrganizationSwitcher',
@@ -51,11 +46,6 @@ export function HotKeys() {
router.push('/account');
});
- useHotkeys('meta+k', (evt) => {
- evt.preventDefault();
- setAssistantOpen(true);
- });
-
useHotkeys('meta+o', (evt) => {
evt.preventDefault();
setShowOrganizationSwitcher(!showOrganizationSwitcher);
diff --git a/apps/app/src/components/mobile-menu.tsx b/apps/app/src/components/mobile-menu.tsx
index 02eeacef3..f1d9c945d 100644
--- a/apps/app/src/components/mobile-menu.tsx
+++ b/apps/app/src/components/mobile-menu.tsx
@@ -50,7 +50,7 @@ export function MobileMenu({
;
+ modal?: boolean;
}
-interface OrganizationAvatarProps {
- name: string | null | undefined;
- logoUrl?: string | null;
- size?: 'sm' | 'default';
- className?: string;
-}
-
-const COLOR_PAIRS = [
- 'bg-sky-100 text-sky-700 dark:bg-sky-900/70 dark:text-sky-200',
- 'bg-blue-100 text-blue-700 dark:bg-blue-900/70 dark:text-blue-200',
- 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/70 dark:text-indigo-200',
- 'bg-purple-100 text-purple-700 dark:bg-purple-900/70 dark:text-purple-200',
- 'bg-fuchsia-100 text-fuchsia-700 dark:bg-fuchsia-900/70 dark:text-fuchsia-200',
- 'bg-pink-100 text-pink-700 dark:bg-pink-900/70 dark:text-pink-200',
- 'bg-rose-100 text-rose-700 dark:bg-rose-900/70 dark:text-rose-200',
- 'bg-red-100 text-red-700 dark:bg-red-900/70 dark:text-red-200',
- 'bg-orange-100 text-orange-700 dark:bg-orange-900/70 dark:text-orange-200',
- 'bg-amber-100 text-amber-700 dark:bg-amber-900/70 dark:text-amber-200',
- 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/70 dark:text-yellow-200',
- 'bg-lime-100 text-lime-700 dark:bg-lime-900/70 dark:text-lime-200',
- 'bg-green-100 text-green-700 dark:bg-green-900/70 dark:text-green-200',
- 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/70 dark:text-emerald-200',
- 'bg-teal-100 text-teal-700 dark:bg-teal-900/70 dark:text-teal-200',
- 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/70 dark:text-cyan-200',
+const DOT_COLORS = [
+ '#0ea5e9', // sky-500
+ '#3b82f6', // blue-500
+ '#6366f1', // indigo-500
+ '#a855f7', // purple-500
+ '#d946ef', // fuchsia-500
+ '#ec4899', // pink-500
+ '#f43f5e', // rose-500
+ '#ef4444', // red-500
+ '#f97316', // orange-500
+ '#f59e0b', // amber-500
+ '#eab308', // yellow-500
+ '#84cc16', // lime-500
+ '#22c55e', // green-500
+ '#10b981', // emerald-500
+ '#14b8a6', // teal-500
+ '#06b6d4', // cyan-500
];
-function OrganizationAvatar({
- name,
- logoUrl,
- size = 'default',
- className,
-}: OrganizationAvatarProps) {
- const sizeClass = size === 'sm' ? 'h-6 w-6' : 'h-8 w-8';
-
- // If logo URL exists, show the image
- if (logoUrl) {
- return (
-
-
-
- );
- }
-
- // Fallback to initials
- const initials = name?.slice(0, 2).toUpperCase() || '';
-
- let colorIndex = 0;
- if (initials.length > 0) {
- const charCodeSum = Array.from(initials).reduce((sum, char) => sum + char.charCodeAt(0), 0);
- colorIndex = charCodeSum % COLOR_PAIRS.length;
- }
-
- const selectedColorClass = COLOR_PAIRS[colorIndex] || COLOR_PAIRS[0];
-
- return (
-
- {initials}
-
- );
+function getOrgColor(name: string | null | undefined): string {
+ if (!name) return DOT_COLORS[0];
+ const charCodeSum = Array.from(name).reduce((sum, char) => sum + char.charCodeAt(0), 0);
+ return DOT_COLORS[charCodeSum % DOT_COLORS.length] || DOT_COLORS[0];
}
export function OrganizationSwitcher({
organizations,
organization,
- isCollapsed = false,
- logoUrls = {},
+ modal = true,
}: OrganizationSwitcherProps) {
const router = useRouter();
- const [isDialogOpen, setIsDialogOpen] = useState(false);
- const [pendingOrgId, setPendingOrgId] = useState(null);
- const [sortOrder, setSortOrder] = useState('alphabetical');
-
- useEffect(() => {
- const savedSortOrder = localStorage.getItem('org-sort-order');
- if (savedSortOrder) {
- setSortOrder(savedSortOrder);
- }
- }, []);
-
- useEffect(() => {
- localStorage.setItem('org-sort-order', sortOrder);
- }, [sortOrder]);
-
- const sortedOrganizations = [...organizations].sort((a, b) => {
- if (sortOrder === 'alphabetical') {
- return a.name.localeCompare(b.name);
- } else if (sortOrder === 'recent') {
- return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
- }
- return 0;
- });
-
- const [showOrganizationSwitcher, setShowOrganizationSwitcher] = useQueryState(
- 'showOrganizationSwitcher',
- {
- history: 'push',
- parse: (value) => value === 'true',
- serialize: (value) => value.toString(),
- },
- );
const { execute, status } = useAction(changeOrganizationAction, {
onSuccess: (result) => {
@@ -144,141 +52,39 @@ export function OrganizationSwitcher({
if (orgId) {
router.push(`/${orgId}/`);
}
- setIsDialogOpen(false);
- setPendingOrgId(null);
- },
- onExecute: (args) => {
- setPendingOrgId(args.input.organizationId);
- },
- onError: () => {
- setPendingOrgId(null);
},
});
- const orgNameCounts = organizations.reduce(
- (acc, org) => {
- if (org.name) {
- acc[org.name] = (acc[org.name] || 0) + 1;
- }
- return acc;
- },
- {} as Record,
- );
-
- const getDisplayName = (org: Organization) => {
- if (!org.name) return `Org (${org.id.substring(0, 4)})`;
- if (orgNameCounts[org.name] > 1) {
- return `${org.name} (${org.id.substring(0, 4)})`;
+ const handleOrgChange = (orgId: string) => {
+ if (orgId !== organization?.id) {
+ execute({ organizationId: orgId });
}
- return org.name;
};
- const currentOrganization = organization;
-
- const handleOrgChange = (org: Organization) => {
- execute({ organizationId: org.id });
+ const handleCreateOrganization = () => {
+ router.push('/setup?intent=create-additional');
};
- const handleOpenChange = (open: boolean) => {
- setShowOrganizationSwitcher(open);
- setIsDialogOpen(open);
- };
+ // Transform organizations to DS OrganizationSelector format
+ const selectorOrgs = organizations.map((org) => ({
+ id: org.id,
+ name: org.name,
+ color: getOrgColor(org.name),
+ }));
+
+ const isExecuting = status === 'executing';
return (
-
-
-
+
);
}
diff --git a/apps/app/src/components/sheets/assistant-sheet.tsx b/apps/app/src/components/sheets/assistant-sheet.tsx
index 65ac063f4..2f3ffb583 100644
--- a/apps/app/src/components/sheets/assistant-sheet.tsx
+++ b/apps/app/src/components/sheets/assistant-sheet.tsx
@@ -4,7 +4,7 @@ import { useMediaQuery } from '@comp/ui/hooks';
import { Sheet, SheetContent } from '@comp/ui/sheet';
import { Drawer, DrawerContent, DrawerTitle } from '@comp/ui/drawer';
-import '@comp/ui/editor.css';
+import '@/styles/editor.css';
import { useQueryState } from 'nuqs';
import Chat from '../ai/chat';
diff --git a/apps/app/src/components/sheets/create-policy-sheet.tsx b/apps/app/src/components/sheets/create-policy-sheet.tsx
index 317383961..4c21badf0 100644
--- a/apps/app/src/components/sheets/create-policy-sheet.tsx
+++ b/apps/app/src/components/sheets/create-policy-sheet.tsx
@@ -1,40 +1,46 @@
'use client';
-import { Button } from '@comp/ui/button';
-import { Drawer, DrawerContent, DrawerTitle } from '@comp/ui/drawer';
import { useMediaQuery } from '@comp/ui/hooks';
-import { ScrollArea } from '@comp/ui/scroll-area';
-import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@comp/ui/sheet';
-import { X } from 'lucide-react';
-import { useQueryState } from 'nuqs';
+import {
+ Drawer,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+ ScrollArea,
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@trycompai/design-system';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { CreateNewPolicyForm } from '../forms/policies/create-new-policy';
export function CreatePolicySheet() {
const isDesktop = useMediaQuery('(min-width: 768px)');
- const [open, setOpen] = useQueryState('create-policy-sheet');
- const isOpen = Boolean(open);
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ const isOpen = searchParams.get('create-policy-sheet') === 'true';
const handleOpenChange = (open: boolean) => {
- setOpen(open ? 'true' : null);
+ const params = new URLSearchParams(searchParams.toString());
+ if (open) {
+ params.set('create-policy-sheet', 'true');
+ } else {
+ params.delete('create-policy-sheet');
+ }
+ const query = params.toString();
+ router.push(query ? `${pathname}?${query}` : pathname);
};
if (isDesktop) {
return (
-
-
- {'Create New Policy'}
-
+
+
+ Create New Policy
-
-
+
@@ -44,9 +50,13 @@ export function CreatePolicySheet() {
return (
- {'Create New Policy'}
-
-
+
+
+ Create New Policy
+
+
+
+
);
diff --git a/apps/app/src/components/sheets/create-risk-sheet.tsx b/apps/app/src/components/sheets/create-risk-sheet.tsx
index 7e8425f39..2ee144f8f 100644
--- a/apps/app/src/components/sheets/create-risk-sheet.tsx
+++ b/apps/app/src/components/sheets/create-risk-sheet.tsx
@@ -1,54 +1,68 @@
'use client';
-import { Button } from '@comp/ui/button';
-import { Drawer, DrawerContent, DrawerTitle } from '@comp/ui/drawer';
import { useMediaQuery } from '@comp/ui/hooks';
-import { ScrollArea } from '@comp/ui/scroll-area';
-import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@comp/ui/sheet';
-import { Member, User } from '@db';
-import { X } from 'lucide-react';
-import { useQueryState } from 'nuqs';
+import type { Member, User } from '@db';
+import {
+ Button,
+ Drawer,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+ ScrollArea,
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@trycompai/design-system';
+import { Add } from '@trycompai/design-system/icons';
+import { useCallback, useState } from 'react';
import { CreateRisk } from '../forms/risks/create-risk-form';
export function CreateRiskSheet({ assignees }: { assignees: (Member & { user: User })[] }) {
const isDesktop = useMediaQuery('(min-width: 768px)');
- const [open, setOpen] = useQueryState('create-risk-sheet');
- const isOpen = Boolean(open);
+ const [isOpen, setIsOpen] = useState(false);
- const handleOpenChange = (open: boolean) => {
- setOpen(open ? 'true' : null);
- };
+ const handleSuccess = useCallback(() => {
+ setIsOpen(false);
+ }, []);
+
+ const trigger = (
+ } onClick={() => setIsOpen(true)}>
+ Create Risk
+
+ );
if (isDesktop) {
return (
-
-
-
- {'Create New Risk'}
-
-
-
-
-
-
-
-
+ <>
+ {trigger}
+
+
+
+ Create New Risk
+
+
+
+
+
+
+ >
);
}
return (
-
- {'Create New Risk'}
-
-
-
-
+ <>
+ {trigger}
+
+
+
+ Create New Risk
+
+
+
+
+
+
+ >
);
}
diff --git a/apps/app/src/components/sidebar-collapse-button.tsx b/apps/app/src/components/sidebar-collapse-button.tsx
index 00ab06bc6..b32efdc70 100644
--- a/apps/app/src/components/sidebar-collapse-button.tsx
+++ b/apps/app/src/components/sidebar-collapse-button.tsx
@@ -6,22 +6,21 @@ import { Button } from '@comp/ui/button';
import { cn } from '@comp/ui/cn';
import { ArrowLeftFromLine } from 'lucide-react';
import { useAction } from 'next-safe-action/hooks';
+import { useRef } from 'react';
-interface SidebarCollapseButtonProps {
- isCollapsed: boolean;
-}
-
-export function SidebarCollapseButton({ isCollapsed }: SidebarCollapseButtonProps) {
- const { setIsCollapsed } = useSidebar();
+export function SidebarCollapseButton() {
+ const { isCollapsed, setIsCollapsed } = useSidebar();
+ const previousIsCollapsedRef = useRef(isCollapsed);
const { execute } = useAction(updateSidebarState, {
onError: () => {
// Revert the optimistic update if the server action fails
- setIsCollapsed(isCollapsed);
+ setIsCollapsed(previousIsCollapsedRef.current);
},
});
const handleToggle = () => {
+ previousIsCollapsedRef.current = isCollapsed;
// Update local state immediately for responsive UI
setIsCollapsed(!isCollapsed);
// Update server state (cookie) in the background
diff --git a/apps/app/src/components/sidebar.tsx b/apps/app/src/components/sidebar.tsx
index 878f16183..eb489b3c8 100644
--- a/apps/app/src/components/sidebar.tsx
+++ b/apps/app/src/components/sidebar.tsx
@@ -111,7 +111,7 @@ export async function Sidebar({
-
+
);
diff --git a/apps/app/src/components/task-items/TaskItemFocusView.tsx b/apps/app/src/components/task-items/TaskItemFocusView.tsx
index 3e44341ef..f28d21db1 100644
--- a/apps/app/src/components/task-items/TaskItemFocusView.tsx
+++ b/apps/app/src/components/task-items/TaskItemFocusView.tsx
@@ -192,9 +192,7 @@ export function TaskItemFocusView({
diff --git a/apps/app/src/styles/editor.css b/apps/app/src/styles/editor.css
new file mode 100644
index 000000000..35ad04f23
--- /dev/null
+++ b/apps/app/src/styles/editor.css
@@ -0,0 +1,220 @@
+@reference "@trycompai/design-system/globals.css";
+
+/* Code block syntax highlighting */
+pre {
+ background: #0d0d0d;
+ color: #fff;
+ font-family: 'JetBrainsMono', monospace;
+ padding: 0.75rem 1rem;
+}
+
+pre code {
+ background: none;
+ color: inherit;
+ font-size: 0.8rem;
+ padding: 0;
+}
+
+pre .hljs-comment,
+pre .hljs-quote {
+ color: #616161;
+}
+
+pre .hljs-variable,
+pre .hljs-template-variable,
+pre .hljs-attribute,
+pre .hljs-tag,
+pre .hljs-name,
+pre .hljs-regexp,
+pre .hljs-link,
+pre .hljs-selector-id,
+pre .hljs-selector-class {
+ color: #f98181;
+}
+
+pre .hljs-number,
+pre .hljs-meta,
+pre .hljs-built_in,
+pre .hljs-builtin-name,
+pre .hljs-literal,
+pre .hljs-type,
+pre .hljs-params {
+ color: #fbbc88;
+}
+
+pre .hljs-string,
+pre .hljs-symbol,
+pre .hljs-bullet {
+ color: #b9f18d;
+}
+
+pre .hljs-title,
+pre .hljs-section {
+ color: #faf594;
+}
+
+pre .hljs-keyword,
+pre .hljs-selector-tag {
+ color: #70cff8;
+}
+
+pre .hljs-emphasis {
+ font-style: italic;
+}
+
+pre .hljs-strong {
+ font-weight: 700;
+}
+
+/* Base editor styles */
+.ProseMirror {
+ @apply w-full leading-relaxed;
+ height: var(--editor-height, 100%);
+ min-height: var(--editor-min-height, 350px);
+}
+
+.ProseMirror:focus {
+ @apply outline-hidden;
+}
+
+/* Typography - with proper spacing */
+.ProseMirror h1 {
+ @apply text-foreground mt-6 mb-4 text-xl font-semibold;
+}
+
+.ProseMirror h2 {
+ @apply text-foreground mt-6 mb-3 text-lg font-semibold;
+}
+
+.ProseMirror h3 {
+ @apply text-foreground mt-5 mb-2 text-base font-semibold;
+}
+
+.ProseMirror h4 {
+ @apply text-foreground mt-4 mb-2 text-sm font-semibold;
+}
+
+.ProseMirror h1:first-child,
+.ProseMirror h2:first-child,
+.ProseMirror h3:first-child,
+.ProseMirror h4:first-child {
+ @apply mt-0;
+}
+
+.ProseMirror p {
+ @apply text-foreground my-3 text-sm leading-relaxed;
+}
+
+/* Preserve empty paragraphs (new lines) */
+.ProseMirror p:empty,
+.ProseMirror p:has(> br:only-child) {
+ min-height: 1.5em;
+}
+
+/* Lists with colored bullets */
+.ProseMirror ul {
+ @apply my-4 ml-6 space-y-2;
+ list-style: none;
+}
+
+.ProseMirror ul > li {
+ @apply relative pl-4 text-sm leading-relaxed;
+}
+
+.ProseMirror ul > li::before {
+ content: '';
+ @apply absolute top-2 left-0 h-1.5 w-1.5 rounded-full bg-foreground;
+}
+
+.ProseMirror ol {
+ @apply my-4 ml-6 space-y-2;
+ list-style: none;
+ counter-reset: item;
+}
+
+.ProseMirror ol > li {
+ @apply relative pl-6 text-sm leading-relaxed;
+ counter-increment: item;
+}
+
+.ProseMirror ol > li::before {
+ content: counter(item) '.';
+ @apply text-muted-foreground absolute left-0 font-medium;
+}
+
+.ProseMirror li > p {
+ @apply m-0;
+}
+
+/* Nested lists */
+.ProseMirror ul ul,
+.ProseMirror ol ol,
+.ProseMirror ul ol,
+.ProseMirror ol ul {
+ @apply my-2;
+}
+
+/* Code blocks */
+.ProseMirror pre {
+ @apply bg-muted my-4 overflow-x-auto rounded-md p-4;
+}
+
+.ProseMirror code {
+ @apply font-mono text-sm;
+}
+
+/* Inline code */
+.ProseMirror :not(pre) > code {
+ @apply bg-muted rounded px-1.5 py-0.5;
+}
+
+/* Blockquotes */
+.ProseMirror blockquote {
+ @apply border-primary text-muted-foreground my-4 border-l-4 pl-4 italic;
+}
+
+/* Task lists */
+.ProseMirror ul[data-type='taskList'] {
+ @apply ml-0 list-none p-0;
+}
+
+.ProseMirror ul[data-type='taskList'] > li {
+ @apply flex items-start gap-2 pl-0;
+}
+
+.ProseMirror ul[data-type='taskList'] > li::before {
+ display: none;
+}
+
+/* Placeholder */
+.ProseMirror p.is-empty::before {
+ @apply text-muted-foreground;
+ content: attr(data-placeholder);
+ float: left;
+ height: 0;
+ pointer-events: none;
+}
+
+/* Tables */
+.ProseMirror table {
+ @apply border-border my-4 w-full border-collapse border;
+}
+
+.ProseMirror th {
+ @apply bg-muted text-sm font-medium;
+}
+
+.ProseMirror td,
+.ProseMirror th {
+ @apply border-border border p-3;
+}
+
+/* Links */
+.ProseMirror a {
+ @apply text-primary underline underline-offset-2;
+}
+
+/* Horizontal rule */
+.ProseMirror hr {
+ @apply border-border my-6;
+}
diff --git a/apps/app/tailwind.config.ts b/apps/app/tailwind.config.ts
deleted file mode 100644
index ecfc384a6..000000000
--- a/apps/app/tailwind.config.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import baseConfig from '@trycompai/ui/tailwind.config';
-import type { Config } from 'tailwindcss';
-
-export default {
- content: [
- './src/**/*.{ts,tsx}',
- '../../packages/ui/src/**/*.{ts,tsx}',
- '../../packages/invoice/src/**/*.{ts,tsx}',
- ],
- presets: [baseConfig],
-} satisfies Config;
diff --git a/apps/portal/eslint.config.mjs b/apps/portal/eslint.config.mjs
new file mode 100644
index 000000000..5a2e25f56
--- /dev/null
+++ b/apps/portal/eslint.config.mjs
@@ -0,0 +1,32 @@
+import { FlatCompat } from '@eslint/eslintrc';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+});
+
+export default [
+ {
+ ignores: [
+ '**/.next/**',
+ '**/dist/**',
+ '**/node_modules/**',
+ '**/coverage/**',
+ '**/.turbo/**',
+ '**/out/**',
+ ],
+ },
+ ...compat.extends('next/core-web-vitals', 'next/typescript'),
+ {
+ rules: {
+ // This repo has existing violations; keep lint actionable while we migrate.
+ '@typescript-eslint/no-explicit-any': 'off',
+ 'react/no-unescaped-entities': 'off',
+ 'prefer-const': 'off',
+ },
+ },
+];
diff --git a/apps/portal/package.json b/apps/portal/package.json
index e2e677493..d75e9c4d1 100644
--- a/apps/portal/package.json
+++ b/apps/portal/package.json
@@ -54,8 +54,8 @@
"db:getschema": "node ../../packages/db/scripts/combine-schemas.js && cp ../../packages/db/dist/schema.prisma prisma/schema.prisma",
"db:migrate": "cd ../../packages/db && bunx prisma migrate dev && cd ../../apps/portal",
"dev": "next dev --turbopack -p 3002",
- "lint": "next lint && prettier --check .",
+ "lint": "eslint . && prettier --check .",
"prebuild": "bun run db:generate",
"start": "next start"
}
-}
\ No newline at end of file
+}
diff --git a/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx b/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx
index a9dfdde40..a92b8a72d 100644
--- a/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx
+++ b/apps/portal/src/app/(app)/(home)/[orgId]/page.tsx
@@ -13,12 +13,14 @@ export default async function OrganizationPage({ params }: { params: Promise<{ o
const { orgId } = await params;
// Auth check with error handling
- const session = await auth.api.getSession({
- headers: await headers(),
- }).catch((error) => {
- console.error('Error getting session:', error);
- redirect('/');
- });
+ const session = await auth.api
+ .getSession({
+ headers: await headers(),
+ })
+ .catch((error) => {
+ console.error('Error getting session:', error);
+ redirect('/');
+ });
if (!session?.user) {
redirect('/auth');
diff --git a/apps/portal/src/app/(app)/(home)/[orgId]/types/index.ts b/apps/portal/src/app/(app)/(home)/[orgId]/types/index.ts
index c132d164f..8c8f8f3a3 100644
--- a/apps/portal/src/app/(app)/(home)/[orgId]/types/index.ts
+++ b/apps/portal/src/app/(app)/(home)/[orgId]/types/index.ts
@@ -88,4 +88,4 @@ export type MDM = {
enrollment_status: string;
name?: string;
server_url?: string;
-};
\ No newline at end of file
+};
diff --git a/apps/portal/src/app/(app)/(home)/page.tsx b/apps/portal/src/app/(app)/(home)/page.tsx
index cf1a277dc..09b76c5d7 100644
--- a/apps/portal/src/app/(app)/(home)/page.tsx
+++ b/apps/portal/src/app/(app)/(home)/page.tsx
@@ -2,12 +2,7 @@ import type { Metadata } from 'next';
import { Suspense } from 'react';
import { Overview } from './components/Overview';
-interface HomePageProps {
- params: Promise<{}>;
- searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
-}
-
-export default function HomePage({ params, searchParams }: HomePageProps) {
+export default function HomePage() {
return (
{/* Add loading states later if Overview becomes complex */}
diff --git a/apps/portal/src/app/(public)/auth/page.tsx b/apps/portal/src/app/(public)/auth/page.tsx
index 7e18fe905..6b4f86e4f 100644
--- a/apps/portal/src/app/(public)/auth/page.tsx
+++ b/apps/portal/src/app/(public)/auth/page.tsx
@@ -2,7 +2,14 @@ import { LoginForm } from '@/app/components/login-form';
import { OtpSignIn } from '@/app/components/otp';
import { env } from '@/env.mjs';
import { Button } from '@comp/ui/button';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@comp/ui/card';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@comp/ui/card';
import { Icons } from '@comp/ui/icons';
import { ArrowRight } from 'lucide-react';
import type { Metadata } from 'next';
diff --git a/apps/portal/src/app/actions/login.ts b/apps/portal/src/app/actions/login.ts
index 919497390..e6362deea 100644
--- a/apps/portal/src/app/actions/login.ts
+++ b/apps/portal/src/app/actions/login.ts
@@ -18,11 +18,11 @@ const handleServerError = (e: Error) => {
if (errorMessage.includes('expired') && errorMessage.includes('otp')) {
return 'OTP code has expired. Please request a new code.';
}
-
+
if (errorMessage.includes('not found') || errorMessage.includes('user not found')) {
return 'No account found with this email address.';
}
-
+
// For other authentication errors, provide a more specific message
if (errorMessage.includes('unauthorized') || errorMessage.includes('authentication')) {
return 'Authentication failed. Please try again.';
@@ -48,7 +48,7 @@ export const login = createSafeActionClient({ handleServerError })
)
.action(async ({ parsedInput }) => {
const headersList = await headers();
-
+
await auth.api.signInEmailOTP({
headers: headersList,
body: {
diff --git a/apps/portal/src/app/api/download-agent/scripts.ts b/apps/portal/src/app/api/download-agent/scripts.ts
index 64e9dec2f..d41b836e3 100644
--- a/apps/portal/src/app/api/download-agent/scripts.ts
+++ b/apps/portal/src/app/api/download-agent/scripts.ts
@@ -1,9 +1,4 @@
import { getPackageFilename, getReadmeContent, getScriptFilename } from './scripts/common';
import { generateMacScript } from './scripts/mac';
-export {
- generateMacScript,
- getPackageFilename,
- getReadmeContent,
- getScriptFilename,
-};
+export { generateMacScript, getPackageFilename, getReadmeContent, getScriptFilename };
diff --git a/apps/portal/src/app/components/login-form.tsx b/apps/portal/src/app/components/login-form.tsx
index f7134bb90..99397b179 100644
--- a/apps/portal/src/app/components/login-form.tsx
+++ b/apps/portal/src/app/components/login-form.tsx
@@ -1,8 +1,8 @@
'use client';
+import { useSearchParams } from 'next/navigation';
import { GoogleSignIn } from './google-sign-in';
import { MicrosoftSignIn } from './microsoft-sign-in';
-import { useSearchParams } from 'next/navigation';
interface LoginFormProps {
inviteCode?: string;
@@ -28,9 +28,13 @@ export function LoginForm({ inviteCode, showGoogle, showMicrosoft }: LoginFormPr
- {showGoogle && }
- {showMicrosoft && }
+ {showGoogle && (
+
+ )}
+ {showMicrosoft && (
+
+ )}
);
-}
\ No newline at end of file
+}
diff --git a/apps/portal/src/app/components/microsoft-sign-in.tsx b/apps/portal/src/app/components/microsoft-sign-in.tsx
index 7ce1e6a56..7c7ae844e 100644
--- a/apps/portal/src/app/components/microsoft-sign-in.tsx
+++ b/apps/portal/src/app/components/microsoft-sign-in.tsx
@@ -97,4 +97,3 @@ export function MicrosoftSignIn({
);
}
-
diff --git a/apps/portal/src/app/layout.tsx b/apps/portal/src/app/layout.tsx
index ec4fc21fe..0f5b82f9a 100644
--- a/apps/portal/src/app/layout.tsx
+++ b/apps/portal/src/app/layout.tsx
@@ -1,5 +1,5 @@
-import { env } from '@/env.mjs';
import { auth } from '@/app/lib/auth';
+import { env } from '@/env.mjs';
import { initializeServer } from '@comp/analytics/server';
import { cn } from '@comp/ui/cn';
import '@comp/ui/globals.css';
diff --git a/apps/portal/src/hooks/use-update-policy.ts b/apps/portal/src/hooks/use-update-policy.ts
index b8ff1238e..43da532df 100644
--- a/apps/portal/src/hooks/use-update-policy.ts
+++ b/apps/portal/src/hooks/use-update-policy.ts
@@ -1,7 +1,7 @@
'use client';
-import type { JSONContent } from '@tiptap/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
+import type { JSONContent } from '@tiptap/react';
interface UpdatePolicyContentInput {
policyId: string;
diff --git a/apps/portal/tsconfig.json b/apps/portal/tsconfig.json
index e3283a6b7..ffedc5da5 100644
--- a/apps/portal/tsconfig.json
+++ b/apps/portal/tsconfig.json
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "ES2017",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -23,108 +19,40 @@
}
],
"paths": {
- "@/*": [
- "./src/*"
- ],
- "@db": [
- "./prisma"
- ],
- "@comp/email": [
- "../../packages/email/index.ts"
- ],
- "@comp/email/*": [
- "../../packages/email/*"
- ],
- "@comp/kv": [
- "../../packages/kv/src/index.ts"
- ],
- "@comp/kv/*": [
- "../../packages/kv/src/*"
- ],
- "@comp/ui": [
- "../../packages/ui/src/components/index.ts"
- ],
- "@comp/ui/*": [
- "../../packages/ui/src/components/*"
- ],
- "@comp/ui/hooks": [
- "../../packages/ui/src/hooks/index.ts"
- ],
- "@comp/ui/hooks/*": [
- "../../packages/ui/src/hooks/*"
- ],
- "@comp/ui/utils/*": [
- "../../packages/ui/src/utils/*"
- ],
- "@comp/ui/cn": [
- "../../packages/ui/src/utils/cn.ts"
- ],
- "@comp/ui/truncate": [
- "../../packages/ui/src/utils/truncate.ts"
- ],
- "@comp/ui/globals.css": [
- "../../packages/ui/src/globals.css"
- ],
- "@comp/ui/editor.css": [
- "../../packages/ui/src/editor.css"
- ],
- "@comp/ui/tailwind.config": [
- "../../packages/ui/tailwind.config.ts"
- ],
- "@comp/utils": [
- "../../packages/utils/src/index.ts"
- ],
- "@comp/utils/*": [
- "../../packages/utils/src/*"
- ],
- "@comp/integrations": [
- "../../packages/integrations/src/index.ts"
- ],
- "@comp/integrations/*": [
- "../../packages/integrations/src/*"
- ],
- "@comp/analytics": [
- "../../packages/analytics/src/index.ts"
- ],
- "@comp/analytics/*": [
- "../../packages/analytics/src/*"
- ],
- "@trycompai/tsconfig": [
- "../../packages/tsconfig"
- ],
- "@trycompai/tsconfig/*": [
- "../../packages/tsconfig/*"
- ],
- "@trycompai/email": [
- "../../packages/email/index.ts"
- ],
- "@trycompai/email/*": [
- "../../packages/email/*"
- ],
- "@trycompai/kv": [
- "../../packages/kv/src/index.ts"
- ],
- "@trycompai/kv/*": [
- "../../packages/kv/src/*"
- ],
- "@trycompai/ui": [
- "../../packages/ui/src/components/index.ts"
- ],
- "@trycompai/ui/*": [
- "../../packages/ui/src/components/*"
- ],
- "@trycompai/utils": [
- "../../packages/utils/src/index.ts"
- ],
- "@trycompai/utils/*": [
- "../../packages/utils/src/*"
- ],
- "@trycompai/analytics": [
- "../../packages/analytics/src/index.ts"
- ],
- "@trycompai/analytics/*": [
- "../../packages/analytics/src/*"
- ]
+ "@/*": ["./src/*"],
+ "@db": ["./prisma"],
+ "@comp/email": ["../../packages/email/index.ts"],
+ "@comp/email/*": ["../../packages/email/*"],
+ "@comp/kv": ["../../packages/kv/src/index.ts"],
+ "@comp/kv/*": ["../../packages/kv/src/*"],
+ "@comp/ui": ["../../packages/ui/src/components/index.ts"],
+ "@comp/ui/*": ["../../packages/ui/src/components/*"],
+ "@comp/ui/hooks": ["../../packages/ui/src/hooks/index.ts"],
+ "@comp/ui/hooks/*": ["../../packages/ui/src/hooks/*"],
+ "@comp/ui/utils/*": ["../../packages/ui/src/utils/*"],
+ "@comp/ui/cn": ["../../packages/ui/src/utils/cn.ts"],
+ "@comp/ui/truncate": ["../../packages/ui/src/utils/truncate.ts"],
+ "@comp/ui/globals.css": ["../../packages/ui/src/globals.css"],
+ "@comp/ui/editor.css": ["../../packages/ui/src/editor.css"],
+ "@comp/ui/tailwind.config": ["../../packages/ui/tailwind.config.ts"],
+ "@comp/utils": ["../../packages/utils/src/index.ts"],
+ "@comp/utils/*": ["../../packages/utils/src/*"],
+ "@comp/integrations": ["../../packages/integrations/src/index.ts"],
+ "@comp/integrations/*": ["../../packages/integrations/src/*"],
+ "@comp/analytics": ["../../packages/analytics/src/index.ts"],
+ "@comp/analytics/*": ["../../packages/analytics/src/*"],
+ "@trycompai/tsconfig": ["../../packages/tsconfig"],
+ "@trycompai/tsconfig/*": ["../../packages/tsconfig/*"],
+ "@trycompai/email": ["../../packages/email/index.ts"],
+ "@trycompai/email/*": ["../../packages/email/*"],
+ "@trycompai/kv": ["../../packages/kv/src/index.ts"],
+ "@trycompai/kv/*": ["../../packages/kv/src/*"],
+ "@trycompai/ui": ["../../packages/ui/src/components/index.ts"],
+ "@trycompai/ui/*": ["../../packages/ui/src/components/*"],
+ "@trycompai/utils": ["../../packages/utils/src/index.ts"],
+ "@trycompai/utils/*": ["../../packages/utils/src/*"],
+ "@trycompai/analytics": ["../../packages/analytics/src/index.ts"],
+ "@trycompai/analytics/*": ["../../packages/analytics/src/*"]
}
},
"include": [
@@ -134,7 +62,5 @@
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
- "exclude": [
- "node_modules"
- ]
+ "exclude": ["node_modules"]
}
diff --git a/bun.lock b/bun.lock
index 95fb996d3..e005d2b3e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -211,6 +211,7 @@
"@trigger.dev/react-hooks": "4.0.6",
"@trigger.dev/sdk": "4.0.6",
"@trycompai/db": "1.3.21",
+ "@trycompai/design-system": "^1.0.28",
"@trycompai/email": "workspace:*",
"@types/canvas-confetti": "^1.9.0",
"@types/react-syntax-highlighter": "^15.5.13",
@@ -647,6 +648,8 @@
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
+ "@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="],
+
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.39.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg=="],
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
@@ -783,16 +786,28 @@
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
+
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
+ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="],
+
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
+ "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="],
+
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
+ "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="],
+
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+ "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="],
+
+ "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="],
+
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
@@ -837,10 +852,16 @@
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="],
+ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="],
+
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
+ "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="],
+
+ "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="],
+
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
@@ -849,6 +870,10 @@
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
+ "@base-ui/react": ["@base-ui/react@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui/utils": "0.2.3", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.3.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg=="],
+
+ "@base-ui/utils": ["@base-ui/utils@0.2.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-/CguQ2PDaOzeVOkllQR8nocJ0FFIDqsWIcURsVmm53QGo8NhFNpePjNlyPIB41luxfOqnG7PU0xicMEw3ls7XQ=="],
+
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
"@better-auth/core": ["@better-auth/core@1.4.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-dQ3hZOkUJzeBXfVEPTm2LVbzmWwka1nqd9KyWmB2OMlMfjr7IdUeBX4T7qJctF67d7QDhlX95jMoxu6JG0Eucw=="],
@@ -877,6 +902,10 @@
"@calcom/embed-snippet": ["@calcom/embed-snippet@1.3.3", "", { "dependencies": { "@calcom/embed-core": "1.5.3" } }, "sha512-pqqKaeLB8R6BvyegcpI9gAyY6Xyx1bKYfWvIGOvIbTpguWyM1BBBVcT9DCeGe8Zw7Ujp5K56ci7isRUrT2Uadg=="],
+ "@carbon/icon-helpers": ["@carbon/icon-helpers@10.70.0", "", { "dependencies": { "@ibm/telemetry-js": "^1.5.0" } }, "sha512-c3w4CMvdentyisIG8WvJf4W5bU39JElrMJvS57DvXUKdWayJqwG9698+ah3PwsNw815u270koIsDv69N6Gsxug=="],
+
+ "@carbon/icons-react": ["@carbon/icons-react@11.72.0", "", { "dependencies": { "@carbon/icon-helpers": "^10.70.0", "@ibm/telemetry-js": "^1.5.0", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=16" } }, "sha512-FMNEFzYwvEd3PwlI0DKmr9V6aj54XbyjM6jQYxgWCHHmp4WhirqIQ99js09WG10SiUv55Uy5VHqE8jWiu3EnRg=="],
+
"@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="],
"@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="],
@@ -975,6 +1004,8 @@
"@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="],
+ "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.51.4", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-AoziS8lRQ3ew/lY5J4JSlzYSN9Fo0oiyMBY37L3Bwq4mOQJT5GSrdZYLFPt6pH1LApDI3ZJceNyx+rHRACZSeQ=="],
+
"@dub/analytics": ["@dub/analytics@0.0.27", "", { "dependencies": { "server-only": "^0.0.1" } }, "sha512-TbLr+sKWiBsMw1GOLpduZI7skXmrLwZlEoVoHyxq66RvMk/5Fl84QoenlhjdxaX1rlHAkWCcjcbAYcCN7skoKw=="],
"@dub/better-auth": ["@dub/better-auth@0.0.6", "", { "dependencies": { "zod": "^3.24.4" } }, "sha512-l7k1PVro6Ib6buBvB/ONlHl1xBH/nU0nbF9m0NawMYNeGhJwmw73JTg6HbhuBgfSRuzZUhI4jRhnldPzejWubg=="],
@@ -983,6 +1014,8 @@
"@dub/embed-react": ["@dub/embed-react@0.0.16", "", { "dependencies": { "@dub/embed-core": "^0.0.16", "class-variance-authority": "^0.7.0", "vite": "5.2.9" }, "peerDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" } }, "sha512-HVo20cEKEX5nRxJsUq6XiUsv2HnmZec2iQbGruWf+Z//OyiUboquaGqOUnNNOmLPdmWONzaYxXYhvAmEjKU1XQ=="],
+ "@ecies/ciphers": ["@ecies/ciphers@0.2.5", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A=="],
+
"@effect/platform": ["@effect/platform@0.90.3", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.33.0", "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.17.7" } }, "sha512-XvQ37yzWQKih4Du2CYladd1i/MzqtgkTPNCaN6Ku6No4CK83hDtXIV/rP03nEoBg2R3Pqgz6gGWmE2id2G81HA=="],
"@electric-sql/client": ["@electric-sql/client@1.0.14", "", { "dependencies": { "@microsoft/fetch-event-source": "^2.0.1" }, "optionalDependencies": { "@rollup/rollup-darwin-arm64": "^4.18.1" } }, "sha512-LtPAfeMxXRiYS0hyDQ5hue2PjljUiK9stvzsVyVb4nwxWQxfOWTSF42bHTs/o5i3x1T4kAQ7mwHpxa4A+f8X7Q=="],
@@ -1077,6 +1110,8 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
+ "@fontsource-variable/plus-jakarta-sans": ["@fontsource-variable/plus-jakarta-sans@5.2.8", "", {}, "sha512-iQecBizIdZxezODNHzOn4SvvRMrZL/S8k4MEXGDynCmUrImVW0VmX+tIAMqnADwH4haXlHSXqMgU6+kcfBQJdw=="],
+
"@google-cloud/precise-date": ["@google-cloud/precise-date@4.0.0", "", {}, "sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA=="],
"@google/genai": ["@google/genai@1.34.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.24.0" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw=="],
@@ -1093,6 +1128,8 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
+ "@ibm/telemetry-js": ["@ibm/telemetry-js@1.10.2", "", { "bin": { "ibmtelemetry": "dist/collect.js" } }, "sha512-F8+/NNUwtm8BuFz18O9KPvIFTFDo8GUSoyhPxPjEpk7nEyEzWGfhIiEPhL00B2NdHRLDSljh3AiCfSnL/tutiQ=="],
+
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
@@ -1297,6 +1334,8 @@
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
+ "@mswjs/interceptors": ["@mswjs/interceptors@0.40.0", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ=="],
+
"@nangohq/frontend": ["@nangohq/frontend@0.53.2", "", { "dependencies": { "@nangohq/types": "0.53.2" } }, "sha512-ZSNY9jHVuF/Qfsu8TJBK3tujsxO+Qi7dHWNFt316Mq3g4od9MwuHefTEs0EtfpUTCB18hNE05QOQWCuD8zO8Aw=="],
"@nangohq/types": ["@nangohq/types@0.53.2", "", { "dependencies": { "axios": "^1.7.9", "json-schema": "0.4.0", "type-fest": "4.32.0" } }, "sha512-G7oC4QsJrmLjAWQmvB7gY8hE0UMr8PofAY/pPsk/0sHIM1YWeealBI7RiPeN4UluArT7w+OoUvMQd+jtrTh9Lw=="],
@@ -1369,6 +1408,8 @@
"@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="],
+ "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
+
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@@ -1413,6 +1454,12 @@
"@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
+ "@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="],
+
+ "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="],
+
+ "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
+
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ=="],
@@ -2135,6 +2182,8 @@
"@trycompai/db": ["@trycompai/db@workspace:packages/db"],
+ "@trycompai/design-system": ["@trycompai/design-system@1.0.28", "", { "dependencies": { "@base-ui/react": "^1.0.0", "@carbon/icons-react": "^11.72.0", "@fontsource-variable/plus-jakarta-sans": "^5.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "next-themes": "^0.4.6", "react-day-picker": "^9.13.0", "react-resizable-panels": "^4.2.0", "recharts": "2.15.4", "shadcn": "^3.6.2", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "tw-animate-css": "^1.4.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "tailwindcss": "^4.0.0" } }, "sha512-oOcysQnZiGJNr7jOWQB3W3H9vW0h69fXjIUpX1XRsPW3uMAcRCUIl+jzm1AQnuuFKm187iq3WTSVnl/+bzqthg=="],
+
"@trycompai/email": ["@trycompai/email@workspace:packages/email"],
"@trycompai/integrations": ["@trycompai/integrations@workspace:packages/integrations"],
@@ -2147,6 +2196,8 @@
"@trycompai/utils": ["@trycompai/utils@workspace:packages/utils"],
+ "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
+
"@tsconfig/node10": ["@tsconfig/node10@1.0.12", "", {}, "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ=="],
"@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="],
@@ -2363,6 +2414,8 @@
"@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
+ "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="],
+
"@types/superagent": ["@types/superagent@8.1.9", "", { "dependencies": { "@types/cookiejar": "^2.1.5", "@types/methods": "^1.1.4", "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ=="],
"@types/supertest": ["@types/supertest@6.0.3", "", { "dependencies": { "@types/methods": "^1.1.4", "@types/superagent": "^8.1.0" } }, "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w=="],
@@ -2383,6 +2436,8 @@
"@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="],
+ "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
+
"@types/validator": ["@types/validator@13.15.10", "", {}, "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA=="],
"@types/webxr": ["@types/webxr@0.5.24", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="],
@@ -2655,7 +2710,7 @@
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
- "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
+ "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
@@ -2903,6 +2958,8 @@
"co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="],
+ "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="],
+
"code-point-at": ["code-point-at@1.1.0", "", {}, "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA=="],
"codepage": ["codepage@1.15.0", "", {}, "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="],
@@ -3109,6 +3166,8 @@
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
+ "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="],
+
"dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
@@ -3237,6 +3296,8 @@
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
+ "eciesjs": ["eciesjs@0.4.16", "", { "dependencies": { "@ecies/ciphers": "^0.2.4", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw=="],
+
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="],
@@ -3543,6 +3604,10 @@
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
+ "fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
+
+ "fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="],
+
"gauge": ["gauge@2.7.4", "", { "dependencies": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", "has-unicode": "^2.0.0", "object-assign": "^4.1.0", "signal-exit": "^3.0.0", "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" } }, "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg=="],
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
@@ -3563,6 +3628,8 @@
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
+ "get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
+
"get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
@@ -3613,6 +3680,8 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+ "graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
+
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
@@ -3669,6 +3738,8 @@
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
+ "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="],
+
"helmet": ["helmet@8.1.0", "", {}, "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg=="],
"help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
@@ -3819,6 +3890,8 @@
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
+ "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="],
+
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
@@ -3827,11 +3900,13 @@
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
+ "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="],
+
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
- "is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
+ "is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
@@ -3841,6 +3916,8 @@
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
+ "is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="],
+
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
@@ -4379,6 +4456,8 @@
"msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
+ "msw": ["msw@2.12.7", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.40.0", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.7.0", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-retd5i3xCZDVWMYjHEVuKTmhqY8lSsxujjVrZiGbbdoxxIBg5S7rCuYy/YQpfrTYIxpd/o0Kyb/3H+1udBMoYg=="],
+
"multer": ["multer@2.0.2", "", { "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", "mkdirp": "^0.5.6", "object-assign": "^4.1.1", "type-is": "^1.6.18", "xtend": "^4.0.2" } }, "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw=="],
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
@@ -4475,6 +4554,8 @@
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
+ "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="],
+
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
"object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="],
@@ -4519,6 +4600,8 @@
"osenv": ["osenv@0.1.5", "", { "dependencies": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" } }, "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g=="],
+ "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
+
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"p-each-series": ["p-each-series@3.0.0", "", {}, "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw=="],
@@ -4579,6 +4662,8 @@
"patchright-core": ["patchright-core@1.57.0", "", { "bin": { "patchright-core": "cli.js" } }, "sha512-um/9Wue7IFAa9UDLacjNgDn62ub5GJe1b1qouvYpELIF9rsFVMNhRo/rRXYajupLwp5xKJ0sSjOV6sw8/HarBQ=="],
+ "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
+
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
@@ -4695,6 +4780,8 @@
"potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="],
+ "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
+
"preact": ["preact@10.28.0", "", {}, "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
@@ -4895,6 +4982,8 @@
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
+ "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
+
"recharts": ["recharts@2.15.0", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.0", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw=="],
"recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="],
@@ -4953,6 +5042,8 @@
"requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
+ "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
+
"resend": ["resend@6.6.0", "", { "dependencies": { "svix": "1.76.1" }, "peerDependencies": { "@react-email/render": "*" }, "optionalPeers": ["@react-email/render"] }, "sha512-d1WoOqSxj5x76JtQMrieNAG1kZkh4NU4f+Je1yq4++JsDpLddhEwnJlNfvkCzvUuZy9ZquWmMMAm2mENd2JvRw=="],
"resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="],
@@ -4971,6 +5062,8 @@
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+ "rettime": ["rettime@0.7.0", "", {}, "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw=="],
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rgbcolor": ["rgbcolor@1.0.1", "", {}, "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw=="],
@@ -5071,6 +5164,8 @@
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
+ "shadcn": ["shadcn@3.6.3", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.17.2", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-j2xlma8PtYLbhvA612/MPOrDYsEp0DIiU1gC0BEbSBqWR6mBgwiKpA21Juq9tSswgUeIfxoUzZX8c7YwcL3ncA=="],
+
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -5205,6 +5300,8 @@
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
+ "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="],
+
"string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -5227,6 +5324,8 @@
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
+ "stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="],
+
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -5293,6 +5392,8 @@
"syncpack": ["syncpack@13.0.4", "", { "dependencies": { "chalk": "^5.4.1", "chalk-template": "^1.1.0", "commander": "^13.1.0", "cosmiconfig": "^9.0.0", "effect": "^3.13.7", "enquirer": "^2.4.1", "fast-check": "^3.23.2", "globby": "^14.1.0", "jsonc-parser": "^3.3.1", "minimatch": "9.0.5", "npm-package-arg": "^12.0.2", "ora": "^8.2.0", "prompts": "^2.4.2", "read-yaml-file": "^2.1.0", "semver": "^7.7.1", "tightrope": "0.2.0", "ts-toolbelt": "^9.6.0" }, "bin": { "syncpack": "dist/bin.js", "syncpack-lint": "dist/bin-lint/index.js", "syncpack-list": "dist/bin-list/index.js", "syncpack-format": "dist/bin-format/index.js", "syncpack-prompt": "dist/bin-prompt/index.js", "syncpack-update": "dist/bin-update/index.js", "syncpack-fix-mismatches": "dist/bin-fix-mismatches/index.js", "syncpack-list-mismatches": "dist/bin-list-mismatches/index.js", "syncpack-set-semver-ranges": "dist/bin-set-semver-ranges/index.js", "syncpack-lint-semver-ranges": "dist/bin-lint-semver-ranges/index.js" } }, "sha512-kJ9VlRxNCsBD5pJAE29oXeBYbPLhEySQmK4HdpsLv81I6fcDDW17xeJqMwiU3H7/woAVsbgq25DJNS8BeiN5+w=="],
+ "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
+
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
"tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="],
@@ -5421,6 +5522,8 @@
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
+ "ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="],
+
"ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="],
"ts-pattern": ["ts-pattern@5.9.0", "", {}, "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg=="],
@@ -5461,6 +5564,8 @@
"turbo-windows-arm64": ["turbo-windows-arm64@2.7.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-1rZk9htm3+iP/rWCf/h4/DFQey9sMs2TJPC4T5QQfwqAdMWsphgrxBuFqHdxczlbBCgbWNhVw0CH2bTxe1/GFg=="],
+ "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
+
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
@@ -5545,6 +5650,8 @@
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
+ "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="],
+
"unzipper": ["unzipper@0.10.14", "", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
@@ -5801,6 +5908,8 @@
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+ "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
"@browserbasehq/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@browserbasehq/stagehand/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
@@ -5851,10 +5960,18 @@
"@discordjs/ws/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
+ "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+
+ "@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
+
+ "@dotenvx/dotenvx/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
+
"@dub/better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@dub/embed-react/vite": ["vite@5.2.9", "", { "dependencies": { "esbuild": "^0.20.1", "postcss": "^8.4.38", "rollup": "^4.13.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw=="],
+ "@ecies/ciphers/@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
+
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
@@ -5935,6 +6052,8 @@
"@next/third-parties/next": ["next@15.5.9", "", { "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.7", "@next/swc-darwin-x64": "15.5.7", "@next/swc-linux-arm64-gnu": "15.5.7", "@next/swc-linux-arm64-musl": "15.5.7", "@next/swc-linux-x64-gnu": "15.5.7", "@next/swc-linux-x64-musl": "15.5.7", "@next/swc-win32-arm64-msvc": "15.5.7", "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg=="],
+ "@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
+
"@novu/js/socket.io-client": ["socket.io-client@4.7.2", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w=="],
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
@@ -6103,12 +6222,24 @@
"@trycompai/db/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+ "@trycompai/design-system/react-day-picker": ["react-day-picker@9.13.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ=="],
+
+ "@trycompai/design-system/react-resizable-panels": ["react-resizable-panels@4.4.0", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-vGH1rIhyDOL4RSWYTx3eatjDohDFIRxJCAXUOaeL9HyamptUnUezqndjMtBo9hQeaq1CIP0NBbc7ZV3lBtlgxA=="],
+
+ "@trycompai/design-system/recharts": ["recharts@2.15.4", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw=="],
+
+ "@trycompai/design-system/tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
+
+ "@trycompai/design-system/vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
+
"@trycompai/email/next": ["next@15.5.9", "", { "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.7", "@next/swc-darwin-x64": "15.5.7", "@next/swc-linux-arm64-gnu": "15.5.7", "@next/swc-linux-arm64-musl": "15.5.7", "@next/swc-linux-x64-gnu": "15.5.7", "@next/swc-linux-x64-musl": "15.5.7", "@next/swc-win32-arm64-msvc": "15.5.7", "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg=="],
"@trycompai/email/resend": ["resend@4.8.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA=="],
"@trycompai/ui/lucide-react": ["lucide-react@0.554.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA=="],
+ "@ts-morph/common/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
+
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@uploadthing/shared/effect": ["effect@3.17.7", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-dpt0ONUn3zzAuul6k4nC/coTTw27AL5nhkORXgTi6NfMPzqWYa1M05oKmOMTxpVSTKepqXVcW9vIwkuaaqx9zA=="],
@@ -6197,14 +6328,22 @@
"decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
+ "degenerator/ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
+
"discord.js/undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="],
+ "dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
+
"dotenv-expand/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"dub/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+ "eciesjs/@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
+
+ "eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
+
"engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
@@ -6391,6 +6530,14 @@
"monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="],
+ "msw/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
+
+ "msw/path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
+
+ "msw/tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="],
+
+ "msw/type-fest": ["type-fest@5.3.1", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg=="],
+
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
@@ -6841,6 +6988,8 @@
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+ "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
"recharts/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"request/form-data": ["form-data@2.3.3", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ=="],
@@ -6879,6 +7028,20 @@
"send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+ "shadcn/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
+
+ "shadcn/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
+
+ "shadcn/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
+
+ "shadcn/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
+
+ "shadcn/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
+
+ "shadcn/validate-npm-package-name": ["validate-npm-package-name@7.0.1", "", {}, "sha512-BM0Upcemlce8/9+HE+/VpWqn3u3mYh6Om/FEC8yPMnEHwf710fW5Q6fhjT1SQyRlZD1G9CJbgfH+rWgAcIvjlQ=="],
+
+ "shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+
"signale/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
"signale/figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="],
@@ -7049,6 +7212,20 @@
"@comp/app/resend/@react-email/render": ["@react-email/render@1.1.2", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3", "react-promise-suspense": "^0.3.4" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw=="],
+ "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
+
+ "@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
+
+ "@dotenvx/dotenvx/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+
+ "@dotenvx/dotenvx/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
+
+ "@dotenvx/dotenvx/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+ "@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
+
+ "@dotenvx/dotenvx/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
+
"@dub/embed-react/vite/esbuild": ["esbuild@0.20.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.20.2", "@esbuild/android-arm": "0.20.2", "@esbuild/android-arm64": "0.20.2", "@esbuild/android-x64": "0.20.2", "@esbuild/darwin-arm64": "0.20.2", "@esbuild/darwin-x64": "0.20.2", "@esbuild/freebsd-arm64": "0.20.2", "@esbuild/freebsd-x64": "0.20.2", "@esbuild/linux-arm": "0.20.2", "@esbuild/linux-arm64": "0.20.2", "@esbuild/linux-ia32": "0.20.2", "@esbuild/linux-loong64": "0.20.2", "@esbuild/linux-mips64el": "0.20.2", "@esbuild/linux-ppc64": "0.20.2", "@esbuild/linux-riscv64": "0.20.2", "@esbuild/linux-s390x": "0.20.2", "@esbuild/linux-x64": "0.20.2", "@esbuild/netbsd-x64": "0.20.2", "@esbuild/openbsd-x64": "0.20.2", "@esbuild/sunos-x64": "0.20.2", "@esbuild/win32-arm64": "0.20.2", "@esbuild/win32-ia32": "0.20.2", "@esbuild/win32-x64": "0.20.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g=="],
"@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
@@ -7245,6 +7422,8 @@
"@trigger.dev/core/socket.io/engine.io": ["engine.io@6.5.5", "", { "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1" } }, "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA=="],
+ "@trycompai/design-system/recharts/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
+
"@trycompai/email/next/@next/env": ["@next/env@15.5.9", "", {}, "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg=="],
"@trycompai/email/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw=="],
@@ -7399,6 +7578,8 @@
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
+ "msw/tough-cookie/tldts": ["tldts@7.0.19", "", { "dependencies": { "tldts-core": "^7.0.19" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA=="],
+
"next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
@@ -7543,6 +7724,8 @@
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "shadcn/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
+
"signale/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"signale/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
@@ -7809,6 +7992,8 @@
"gaxios/rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+ "msw/tough-cookie/tldts/tldts-core": ["tldts-core@7.0.19", "", {}, "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A=="],
+
"node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"npm/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
@@ -7883,6 +8068,8 @@
"semantic-release/aggregate-error/clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+ "shadcn/open/wsl-utils/is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
+
"signale/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"signale/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
diff --git a/packages/integration-platform/.prettierignore b/packages/integration-platform/.prettierignore
new file mode 100644
index 000000000..50760c292
--- /dev/null
+++ b/packages/integration-platform/.prettierignore
@@ -0,0 +1,3 @@
+dist
+node_modules
+.turbo
diff --git a/packages/integration-platform/SELF_HOSTING.md b/packages/integration-platform/SELF_HOSTING.md
index 33fdb6e7a..3fd82b136 100644
--- a/packages/integration-platform/SELF_HOSTING.md
+++ b/packages/integration-platform/SELF_HOSTING.md
@@ -34,6 +34,7 @@ OAuth integrations (GitHub, Google Workspace, GCP, etc.) require **platform-leve
6. Click **Generate a new client secret** → Copy it
**Add to platform:**
+
- Go to `/admin/integrations` in your deployment
- Find **GitHub** → Click **Configure**
- Paste Client ID and Client Secret → Save
@@ -73,11 +74,13 @@ OAuth integrations (GitHub, Google Workspace, GCP, etc.) require **platform-leve
**Add to platform:**
**For Google Workspace:**
+
- Go to `/admin/integrations`
- Find **Google Workspace** → **Configure**
- Paste Client ID and Client Secret → Save
**For GCP (use same credentials):**
+
- Find **Google Cloud Platform** → **Configure**
- Paste the **same** Client ID and Client Secret → Save
@@ -97,6 +100,7 @@ OAuth integrations (GitHub, Google Workspace, GCP, etc.) require **platform-leve
4. Create → Copy **Client ID** and **Client Secret**
**Add to platform:**
+
- Go to `/admin/integrations`
- Find **Linear** → **Configure**
- Paste Client ID and Client Secret → Save
@@ -115,6 +119,7 @@ OAuth integrations (GitHub, Google Workspace, GCP, etc.) require **platform-leve
4. Create → Copy **Client ID**, **Client Secret**, and **Integration Slug**
**Add to platform:**
+
- Go to `/admin/integrations`
- Find **Vercel** → **Configure**
- Paste Client ID and Client Secret
@@ -135,6 +140,7 @@ OAuth integrations (GitHub, Google Workspace, GCP, etc.) require **platform-leve
- App Name (your Rippling app identifier)
**Add to platform:**
+
- Go to `/admin/integrations`
- Find **Rippling** → **Configure**
- Paste Client ID and Client Secret
@@ -148,10 +154,12 @@ OAuth integrations (GitHub, Google Workspace, GCP, etc.) require **platform-leve
These don't require platform setup - users provide their own credentials:
### AWS
+
- **Auth**: IAM Role (users create their own role)
- **No platform setup needed**
### Azure
+
- **Auth**: Service Principal (users create their own)
- **No platform setup needed**
@@ -172,6 +180,7 @@ These don't require platform setup - users provide their own credentials:
**Problem:** Users get redirected to an error page after OAuth
**Possible causes:**
+
1. **Callback URL mismatch**: Make sure the redirect URI in the OAuth app matches your deployment URL exactly
2. **Wrong client secret**: Double-check you copied the secret correctly
3. **API not enabled**: Some providers require enabling APIs (Google Workspace, GCP)
@@ -186,6 +195,7 @@ Check API logs for the OAuth callback error message.
**Problem:** Google shows a warning screen during OAuth
**This is normal** for:
+
- Development/testing
- Before Google verification
@@ -209,6 +219,7 @@ Users can click **Advanced** → **Go to [app name] (unsafe)** to proceed.
### Principle of Least Privilege
OAuth credentials grant broad access, but:
+
- We only request the minimum scopes needed
- Checks only perform read operations
- User IAM roles further limit actual access
@@ -216,10 +227,12 @@ OAuth credentials grant broad access, but:
### Callback URL
Always use **HTTPS** in production:
+
- ✅ `https://yourapp.com/v1/integrations/oauth/callback`
- ❌ `http://yourapp.com/...` (insecure)
For local dev:
+
- `http://localhost:3000/v1/integrations/oauth/callback` is fine
---
@@ -227,14 +240,15 @@ For local dev:
## Summary
**Platform Admin (you) does:**
+
1. Create OAuth apps with each provider (one-time, ~5-10 min each)
2. Add Client ID/Secret to `/admin/integrations`
3. That's it!
**End Users do:**
+
1. Click "Connect"
2. Sign in with their account
3. Done!
**No environment variables, no secret management, no complexity.** All credentials are encrypted in the database and managed via the admin UI.
-
diff --git a/packages/integration-platform/src/manifests/github/checks/branch-protection.ts b/packages/integration-platform/src/manifests/github/checks/branch-protection.ts
index 51c2b9cdd..19bbc2271 100644
--- a/packages/integration-platform/src/manifests/github/checks/branch-protection.ts
+++ b/packages/integration-platform/src/manifests/github/checks/branch-protection.ts
@@ -156,7 +156,9 @@ export const branchProtectionCheck: IntegrationCheck = {
continue;
}
- ctx.log(`Checking ${branchesToCheck.length} branches on ${repo.full_name}: ${branchesToCheck.join(', ')}`);
+ ctx.log(
+ `Checking ${branchesToCheck.length} branches on ${repo.full_name}: ${branchesToCheck.join(', ')}`,
+ );
// Collect results for all branches in this repo
const branchResults: Record<
@@ -172,154 +174,154 @@ export const branchProtectionCheck: IntegrationCheck = {
for (const branchToCheck of branchesToCheck) {
ctx.log(`Checking branch "${branchToCheck}" on ${repo.full_name}`);
- // Fetch recent PRs in parallel while we check protection
- const pullRequestsPromise = fetchRecentPullRequests({
- repoFullName: repo.full_name,
- baseBranch: branchToCheck,
- });
+ // Fetch recent PRs in parallel while we check protection
+ const pullRequestsPromise = fetchRecentPullRequests({
+ repoFullName: repo.full_name,
+ baseBranch: branchToCheck,
+ });
- // Helper to check if a branch matches ruleset conditions
- const branchMatchesRuleset = (ruleset: GitHubRuleset, branch: string): boolean => {
- if (!ruleset.conditions?.ref_name) return true;
- const includes = ruleset.conditions.ref_name.include || [];
- const excludes = ruleset.conditions.ref_name.exclude || [];
+ // Helper to check if a branch matches ruleset conditions
+ const branchMatchesRuleset = (ruleset: GitHubRuleset, branch: string): boolean => {
+ if (!ruleset.conditions?.ref_name) return true;
+ const includes = ruleset.conditions.ref_name.include || [];
+ const excludes = ruleset.conditions.ref_name.exclude || [];
- for (const pattern of excludes) {
- if (pattern === `refs/heads/${branch}` || pattern === `~DEFAULT_BRANCH`) {
- return false;
+ for (const pattern of excludes) {
+ if (pattern === `refs/heads/${branch}` || pattern === `~DEFAULT_BRANCH`) {
+ return false;
+ }
}
- }
- if (includes.length === 0) return true;
- for (const pattern of includes) {
- if (
- pattern === `refs/heads/${branch}` ||
- pattern === '~ALL' ||
- (pattern === '~DEFAULT_BRANCH' && branch === repo.default_branch)
- ) {
- return true;
+ if (includes.length === 0) return true;
+ for (const pattern of includes) {
+ if (
+ pattern === `refs/heads/${branch}` ||
+ pattern === '~ALL' ||
+ (pattern === '~DEFAULT_BRANCH' && branch === repo.default_branch)
+ ) {
+ return true;
+ }
}
- }
- return false;
- };
+ return false;
+ };
- let isProtected = false;
- let protectionEvidence: Record
= {};
- let protectionDescription = '';
+ let isProtected = false;
+ let protectionEvidence: Record = {};
+ let protectionDescription = '';
- // Strategy 1: Try the unified rules endpoint
- ctx.log(`[Strategy 1] Trying /repos/${repo.full_name}/rules/branches/${branchToCheck}`);
- try {
- const rules = await ctx.fetch(
- `/repos/${repo.full_name}/rules/branches/${branchToCheck}`,
- );
+ // Strategy 1: Try the unified rules endpoint
+ ctx.log(`[Strategy 1] Trying /repos/${repo.full_name}/rules/branches/${branchToCheck}`);
+ try {
+ const rules = await ctx.fetch(
+ `/repos/${repo.full_name}/rules/branches/${branchToCheck}`,
+ );
- ctx.log(
- `[Strategy 1] Got ${rules.length} rules: ${JSON.stringify(rules.map((r) => r.type))}`,
- );
+ ctx.log(
+ `[Strategy 1] Got ${rules.length} rules: ${JSON.stringify(rules.map((r) => r.type))}`,
+ );
- const hasPullRequestRule = rules.some((r) => r.type === 'pull_request');
- const hasNonFastForward = rules.some((r) => r.type === 'non_fast_forward');
-
- if (rules.length > 0 && (hasPullRequestRule || hasNonFastForward)) {
- isProtected = true;
- const protectionTypes: string[] = [];
- if (hasPullRequestRule) protectionTypes.push('pull request reviews');
- if (hasNonFastForward) protectionTypes.push('non-fast-forward');
-
- const rulesetSources = [
- ...new Set(rules.filter((r) => r.ruleset_source).map((r) => r.ruleset_source)),
- ];
-
- protectionDescription = `Branch "${branchToCheck}" is protected with: ${protectionTypes.join(', ')}. ${rulesetSources.length > 0 ? `Source: ${rulesetSources.join(', ')}` : ''}`;
- protectionEvidence = {
- source: 'rules_endpoint',
- branch: branchToCheck,
- rules,
- rule_types: rules.map((r) => r.type),
- ruleset_sources: rulesetSources,
- };
- ctx.log(`[Strategy 1] SUCCESS - Found protection via rules endpoint`);
- } else {
- ctx.log(`[Strategy 1] No PR or non-fast-forward rules found`);
+ const hasPullRequestRule = rules.some((r) => r.type === 'pull_request');
+ const hasNonFastForward = rules.some((r) => r.type === 'non_fast_forward');
+
+ if (rules.length > 0 && (hasPullRequestRule || hasNonFastForward)) {
+ isProtected = true;
+ const protectionTypes: string[] = [];
+ if (hasPullRequestRule) protectionTypes.push('pull request reviews');
+ if (hasNonFastForward) protectionTypes.push('non-fast-forward');
+
+ const rulesetSources = [
+ ...new Set(rules.filter((r) => r.ruleset_source).map((r) => r.ruleset_source)),
+ ];
+
+ protectionDescription = `Branch "${branchToCheck}" is protected with: ${protectionTypes.join(', ')}. ${rulesetSources.length > 0 ? `Source: ${rulesetSources.join(', ')}` : ''}`;
+ protectionEvidence = {
+ source: 'rules_endpoint',
+ branch: branchToCheck,
+ rules,
+ rule_types: rules.map((r) => r.type),
+ ruleset_sources: rulesetSources,
+ };
+ ctx.log(`[Strategy 1] SUCCESS - Found protection via rules endpoint`);
+ } else {
+ ctx.log(`[Strategy 1] No PR or non-fast-forward rules found`);
+ }
+ } catch (err) {
+ const errorMsg = err instanceof Error ? err.message : String(err);
+ ctx.warn(`[Strategy 1] FAILED: ${errorMsg}`);
}
- } catch (err) {
- const errorMsg = err instanceof Error ? err.message : String(err);
- ctx.warn(`[Strategy 1] FAILED: ${errorMsg}`);
- }
- // Strategy 2: Check rulesets directly
- if (!isProtected) {
- ctx.log(`[Strategy 2] Trying /repos/${repo.full_name}/rulesets`);
- try {
- const rulesets = await ctx.fetch(`/repos/${repo.full_name}/rulesets`);
-
- ctx.log(`[Strategy 2] Got ${rulesets.length} rulesets`);
+ // Strategy 2: Check rulesets directly
+ if (!isProtected) {
+ ctx.log(`[Strategy 2] Trying /repos/${repo.full_name}/rulesets`);
+ try {
+ const rulesets = await ctx.fetch(`/repos/${repo.full_name}/rulesets`);
- const applicableRulesets = rulesets.filter(
- (rs) =>
- rs.enforcement === 'active' &&
- rs.target === 'branch' &&
- branchMatchesRuleset(rs, branchToCheck),
- );
+ ctx.log(`[Strategy 2] Got ${rulesets.length} rulesets`);
- ctx.log(
- `[Strategy 2] ${applicableRulesets.length} active rulesets apply to "${branchToCheck}"`,
- );
+ const applicableRulesets = rulesets.filter(
+ (rs) =>
+ rs.enforcement === 'active' &&
+ rs.target === 'branch' &&
+ branchMatchesRuleset(rs, branchToCheck),
+ );
- for (const rs of applicableRulesets) {
ctx.log(
- `[Strategy 2] Ruleset "${rs.name}": rules=${JSON.stringify(rs.rules?.map((r) => r.type))}`,
+ `[Strategy 2] ${applicableRulesets.length} active rulesets apply to "${branchToCheck}"`,
);
- }
- for (const ruleset of applicableRulesets) {
- const hasPullRequest = ruleset.rules?.some((r) => r.type === 'pull_request');
- if (hasPullRequest) {
- isProtected = true;
- const pullRequestRule = ruleset.rules?.find((r) => r.type === 'pull_request');
- protectionDescription = `Branch "${branchToCheck}" is protected by ruleset "${ruleset.name}" requiring pull request reviews.`;
- protectionEvidence = {
- source: 'rulesets_endpoint',
- branch: branchToCheck,
- ruleset_name: ruleset.name,
- ruleset_id: ruleset.id,
- enforcement: ruleset.enforcement,
- rules: ruleset.rules,
- pull_request_params: pullRequestRule?.parameters,
- };
- break;
+ for (const rs of applicableRulesets) {
+ ctx.log(
+ `[Strategy 2] Ruleset "${rs.name}": rules=${JSON.stringify(rs.rules?.map((r) => r.type))}`,
+ );
}
+
+ for (const ruleset of applicableRulesets) {
+ const hasPullRequest = ruleset.rules?.some((r) => r.type === 'pull_request');
+ if (hasPullRequest) {
+ isProtected = true;
+ const pullRequestRule = ruleset.rules?.find((r) => r.type === 'pull_request');
+ protectionDescription = `Branch "${branchToCheck}" is protected by ruleset "${ruleset.name}" requiring pull request reviews.`;
+ protectionEvidence = {
+ source: 'rulesets_endpoint',
+ branch: branchToCheck,
+ ruleset_name: ruleset.name,
+ ruleset_id: ruleset.id,
+ enforcement: ruleset.enforcement,
+ rules: ruleset.rules,
+ pull_request_params: pullRequestRule?.parameters,
+ };
+ break;
+ }
+ }
+ } catch (err) {
+ const errorMsg = err instanceof Error ? err.message : String(err);
+ ctx.warn(`[Strategy 2] FAILED: ${errorMsg}`);
}
- } catch (err) {
- const errorMsg = err instanceof Error ? err.message : String(err);
- ctx.warn(`[Strategy 2] FAILED: ${errorMsg}`);
}
- }
- // Strategy 3: Try legacy branch protection endpoint
- if (!isProtected) {
- ctx.log(
- `[Strategy 3] Trying /repos/${repo.full_name}/branches/${branchToCheck}/protection`,
- );
- try {
- const protection = await ctx.fetch(
- `/repos/${repo.full_name}/branches/${branchToCheck}/protection`,
+ // Strategy 3: Try legacy branch protection endpoint
+ if (!isProtected) {
+ ctx.log(
+ `[Strategy 3] Trying /repos/${repo.full_name}/branches/${branchToCheck}/protection`,
);
+ try {
+ const protection = await ctx.fetch(
+ `/repos/${repo.full_name}/branches/${branchToCheck}/protection`,
+ );
- isProtected = true;
- protectionDescription = `Branch "${branchToCheck}" requires ${protection.required_pull_request_reviews?.required_approving_review_count || 0} approving review(s) (legacy protection).`;
- protectionEvidence = {
- source: 'legacy_branch_protection',
- branch: branchToCheck,
- protection_rules: protection,
- };
- ctx.log(`[Strategy 3] SUCCESS - Found legacy branch protection`);
- } catch (err) {
- const errorMsg = err instanceof Error ? err.message : String(err);
- ctx.warn(`[Strategy 3] FAILED: ${errorMsg}`);
+ isProtected = true;
+ protectionDescription = `Branch "${branchToCheck}" requires ${protection.required_pull_request_reviews?.required_approving_review_count || 0} approving review(s) (legacy protection).`;
+ protectionEvidence = {
+ source: 'legacy_branch_protection',
+ branch: branchToCheck,
+ protection_rules: protection,
+ };
+ ctx.log(`[Strategy 3] SUCCESS - Found legacy branch protection`);
+ } catch (err) {
+ const errorMsg = err instanceof Error ? err.message : String(err);
+ ctx.warn(`[Strategy 3] FAILED: ${errorMsg}`);
+ }
}
- }
// Wait for PR fetch to complete
const pullRequests = await pullRequestsPromise;
diff --git a/packages/integration-platform/src/manifests/jumpcloud/index.ts b/packages/integration-platform/src/manifests/jumpcloud/index.ts
index db21e8356..73299e0c4 100644
--- a/packages/integration-platform/src/manifests/jumpcloud/index.ts
+++ b/packages/integration-platform/src/manifests/jumpcloud/index.ts
@@ -43,7 +43,8 @@ export const manifest: IntegrationManifest = {
type: 'password',
required: true,
placeholder: 'Your JumpCloud API key',
- helpText: 'https://jumpcloud.com/university/resources/guided-simulations/generating-a-new-api-key',
+ helpText:
+ 'https://jumpcloud.com/university/resources/guided-simulations/generating-a-new-api-key',
},
],
diff --git a/packages/integration-platform/src/registry/index.ts b/packages/integration-platform/src/registry/index.ts
index e7638c47e..6aa409be6 100644
--- a/packages/integration-platform/src/registry/index.ts
+++ b/packages/integration-platform/src/registry/index.ts
@@ -7,6 +7,7 @@ import type {
} from '../types';
// Import all manifests (each in its own folder)
+import { manifest as aikidoManifest } from '../manifests/aikido';
import { awsManifest } from '../manifests/aws';
import { azureManifest } from '../manifests/azure';
import { gcpManifest } from '../manifests/gcp';
@@ -15,7 +16,6 @@ import { googleWorkspaceManifest } from '../manifests/google-workspace';
import { manifest as jumpcloudManifest } from '../manifests/jumpcloud';
import { ripplingManifest } from '../manifests/rippling';
import { vercelManifest } from '../manifests/vercel';
-import { manifest as aikidoManifest } from '../manifests/aikido';
// ============================================================================
// Registry Implementation
diff --git a/packages/integration-platform/src/task-mappings.ts b/packages/integration-platform/src/task-mappings.ts
index aa4823af6..c6f921ff1 100644
--- a/packages/integration-platform/src/task-mappings.ts
+++ b/packages/integration-platform/src/task-mappings.ts
@@ -222,13 +222,13 @@ export const TASK_TEMPLATE_INFO: Record<
TaskTemplateId,
{ name: string; description: string; department: string; frequency: string }
> = {
- 'frk_tt_68407ae5274a64092c305104': {
+ frk_tt_68407ae5274a64092c305104: {
name: 'Secure Secrets',
description: `Use your cloud providers default secret manager for storing secrets. Don't commit secrets to Git and...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_6849c1a1038c3f18cfff47bf': {
+ frk_tt_6849c1a1038c3f18cfff47bf: {
name: 'Utility Monitoring',
description: `Maintain a list of approved privileged utilities (e.g. iptables, tcpdump, disk‑encryption tools)
@@ -236,25 +236,25 @@ Sh...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68406951bd282273ebe286cc': {
+ frk_tt_68406951bd282273ebe286cc: {
name: 'Employee Verification',
description: `Maintain a list of reference checks you made for every new hire. Verify the identity of every new hi...`,
department: 'hr',
frequency: 'yearly',
},
- 'frk_tt_68406e7abae2a9b16c2cc197': {
+ frk_tt_68406e7abae2a9b16c2cc197: {
name: 'Planning',
description: `Make sure you have point in time recovery / backups enabled and that you test this works on an annua...`,
department: 'gov',
frequency: 'yearly',
},
- 'frk_tt_68406a514e90bb6e32e0b107': {
+ frk_tt_68406a514e90bb6e32e0b107: {
name: 'Contact Information',
description: `You need to show what services/software you offer and provide clear instructions on how your custome...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68406f411fe27e47a0d6d5f3': {
+ frk_tt_68406f411fe27e47a0d6d5f3: {
name: 'TLS / HTTPS',
description: `Ensure TLS / HTTPS is enabled.
@@ -262,19 +262,19 @@ Upload a screenshot from SSL Labs to show this is enabled....`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68406e353df3bc002994acef': {
+ frk_tt_68406e353df3bc002994acef: {
name: 'Secure Code',
description: `Ensure dependabot or it's equivalent is enabled to automatically identify insecure or patched depend...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68406af04a4acb93083413b9': {
+ frk_tt_68406af04a4acb93083413b9: {
name: 'Monitoring & Alerting',
description: `Ensure you have logging enabled in cloud environments (e.g. Google Cloud or Vercel) and review it pe...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_686b51339d7e9f8ef2081a70': {
+ frk_tt_686b51339d7e9f8ef2081a70: {
name: 'Data Masking',
description: `Hide Sensitive fields
@@ -282,37 +282,37 @@ PCI: Mask PAN when displayed outside the secure CDE, ensuring only truncated ...
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68406d64f09f13271c14dd01': {
+ frk_tt_68406d64f09f13271c14dd01: {
name: 'Code Changes',
description: `Enable branch protection on your main branch to prevent direct pushes and enforce pull requests. Ens...`,
department: 'gov',
frequency: 'yearly',
},
- 'frk_tt_684076a02261faf3d331289d': {
+ frk_tt_684076a02261faf3d331289d: {
name: 'Publish Policies',
description: `Make sure all of the policies in Comp AI have been published and all employees have signed/agreed to...`,
department: 'gov',
frequency: 'yearly',
},
- 'frk_tt_68406903839203801ac8041a': {
+ frk_tt_68406903839203801ac8041a: {
name: 'Device List',
description: `Keep and maintain a list of your devices (laptops/servers). If you install the Comp AI agent on your...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68b59e7a29bec89c57014868': {
+ frk_tt_68b59e7a29bec89c57014868: {
name: 'Statement of Applicability',
description: `The Statement of Applicability identifies relevant ISO 27001 Annex A controls, explains their inclus...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68406d2e86acc048d1774ea6': {
+ frk_tt_68406d2e86acc048d1774ea6: {
name: 'App Availability',
description: `Make sure your website or app doesn't slow down or crash because too many people are using it, or if...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68406eedf0f0ddd220ea19c2': {
+ frk_tt_68406eedf0f0ddd220ea19c2: {
name: 'Sanitized Inputs',
description: `Implement input validation and sanitization using libraries such as Zod, Pydantic, or equivalent.
@@ -320,37 +320,37 @@ PCI: Mask PAN when displayed outside the secure CDE, ensuring only truncated ...
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_6840796f77d8a0dff53f947a': {
+ frk_tt_6840796f77d8a0dff53f947a: {
name: 'Secure Devices',
description: `Ensure all devices have BitLocker/FileVault enabled, screen lock enabled after 5 minutes (for mac) o...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68b5ce9dd597ac7d650e4915': {
+ frk_tt_68b5ce9dd597ac7d650e4915: {
name: 'Reassess Legal Basis for Processing',
description: `Review and document the lawful basis for each processing activity under GDPR Article 6 (e.g., consen...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68b5ce9b5393ae083c3beadf': {
+ frk_tt_68b5ce9b5393ae083c3beadf: {
name: 'Appoint or Review Data Protection Officer',
description: `Assess whether your organization is required to appoint a Data Protection Officer (DPO) under GDPR (...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68e52b2618cb9d9722c6edfd': {
+ frk_tt_68e52b2618cb9d9722c6edfd: {
name: 'Internal Security Audit',
description: `Upload evidence of a recent internal information security audit - e.g., audit plan/scope, auditor in...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68cc327ff5d3130a1b42420b': {
+ frk_tt_68cc327ff5d3130a1b42420b: {
name: 'AI MS Communication Plan',
description: `Document and maintain a plan outlining internal and external communication relevant to the AI MS: wh...`,
department: 'hr',
frequency: 'yearly',
},
- 'frk_tt_68c3248edf65e750909dfd07': {
+ frk_tt_68c3248edf65e750909dfd07: {
name: 'Attestation of Compliance',
description: `Download and complete the AoC form. Once complete, upload the file here.
@@ -358,73 +358,73 @@ For Merchants - https://d...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68c332c3bc6d696bb61e7351': {
+ frk_tt_68c332c3bc6d696bb61e7351: {
name: 'Self-Assessment Questionnaires',
description: `- SAQ A: For merchants fully outsourcing all card data functions to PCI-validated 3rd parties (you r...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68b5ce9d508cacf8e4517b56': {
+ frk_tt_68b5ce9d508cacf8e4517b56: {
name: 'Review International Data Transfers',
description: `Review and document all international data transfers to ensure compliance with GDPR Chapter V. Confi...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68cc25658bacb2ccff56adf9': {
+ frk_tt_68cc25658bacb2ccff56adf9: {
name: 'AI Context Register',
description: `Maintain a register of internal and external issues relevant to AI systems, including organizational...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_6840791cac0a7b780dbaf932': {
+ frk_tt_6840791cac0a7b780dbaf932: {
name: 'Public Policies',
description: `Add a comment with links to your privacy policy / terms of service. Ensure Privacy policy has a for...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc27431d1266e3c77d7c0f': {
+ frk_tt_68cc27431d1266e3c77d7c0f: {
name: 'Stakeholder Register / Interested Parties Log',
description: `Document a register of interested parties (e.g., regulators, customers, employees, partners, societa...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68e52b26bf0e656af9e4e9c3': {
+ frk_tt_68e52b26bf0e656af9e4e9c3: {
name: 'Encryption at Rest',
description: `Upload evidence that all data stores are encrypted at rest -cloud console screenshots showing encryp...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68b5ce9c6c1bdb171870f623': {
+ frk_tt_68b5ce9c6c1bdb171870f623: {
name: 'Manage Third-party and EU Representative Relationships',
description: `Ensure all third-party processors have GDPR-compliant Data Processing Agreements (DPAs) in place. If...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68e52a484cad0014de7a628f': {
+ frk_tt_68e52a484cad0014de7a628f: {
name: 'Separation of Environments',
description: `Upload proof that dev/test/staging and production are segregated - e.g., a cloud console screenshot ...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68406b4f40c87c12ae0479ce': {
+ frk_tt_68406b4f40c87c12ae0479ce: {
name: 'Incident Response',
description: `Keep a record of all security incidents and how they were resolved. If there haven't been any, add a...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68c8309516fdbc514404988d': {
+ frk_tt_68c8309516fdbc514404988d: {
name: 'Board Meetings & Independence',
description: `Submit the most recent board (or management) meeting agenda and minutes covering security topics. In...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68cc2ce9cb0d2a4774975ace': {
+ frk_tt_68cc2ce9cb0d2a4774975ace: {
name: 'AI MS Roles & Responsibilities Assignment',
description: `Define and communicate responsibilities for the AI MS, including who ensures compliance with 42001 r...`,
department: 'hr',
frequency: 'yearly',
},
- 'frk_tt_6849aad98c50d734dd904d98': {
+ frk_tt_6849aad98c50d734dd904d98: {
name: 'Diagramming',
description: `Architecture Diagram: Draw a single‑page diagram (Figma, Draw.io, Lucidchart—whatever is fastest)
@@ -432,79 +432,79 @@ F...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc2f30b51920e5515465e6': {
+ frk_tt_68cc2f30b51920e5515465e6: {
name: 'AI System Impact Assessment Procedure',
description: `Define a process to identify/assess potential consequences of AI system deployment, intended use, an...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc2fa423533e602e4dee25': {
+ frk_tt_68cc2fa423533e602e4dee25: {
name: 'AI Objectives Register',
description: `Define measurable AI objectives (aligned to AI Policy and compliance requirements), assign resources...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68cc301a8fd914534ab95b11': {
+ frk_tt_68cc301a8fd914534ab95b11: {
name: 'AI System Change Log',
description: `Maintain a documented log of changes to the AI management system, including model retraining, policy...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc30fbbeabb8a4c2f56082': {
+ frk_tt_68cc30fbbeabb8a4c2f56082: {
name: 'AI MS Resource Allocation Record',
description: `Maintain documented evidence of resources allocated for the AI MS, including skills, tools, infrastr...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc395b90e179fe3209b795': {
+ frk_tt_68cc395b90e179fe3209b795: {
name: 'AI MS Operational Control Procedure',
description: `Document and implement an AI Operational Controls procedure defining process criteria, monitoring ef...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc3f427e309607f1ad5ba4': {
+ frk_tt_68cc3f427e309607f1ad5ba4: {
name: 'AI Risk Treatment Implementation Record',
description: `Maintain documented evidence of risk treatment implementation corresponding to the treatment plan. R...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc3ffab3fb703917f51e19': {
+ frk_tt_68cc3ffab3fb703917f51e19: {
name: 'AI Impact Assessment Log',
description: `Conduct AI system impact assessments at planned intervals or when significant changes occur. Documen...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc4190caf0b458effcee6e': {
+ frk_tt_68cc4190caf0b458effcee6e: {
name: 'AI MS Internal Audit Program',
description: `Establish and maintain an internal audit program for the AI MS. Define frequency, methods, auditor r...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc41ad7cbd2839c0bc7104': {
+ frk_tt_68cc41ad7cbd2839c0bc7104: {
name: 'AI Internal Audit Reports',
description: `Conduct AI MS audits at planned intervals. Maintain documented evidence of objectives, criteria, sco...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68cc459ed57b70a4cb631e72': {
+ frk_tt_68cc459ed57b70a4cb631e72: {
name: 'AI MS Continual Improvement Log',
description: `Maintain a documented continual improvement log for the AI MS. Capture decisions, actions, and imple...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68d28f68f117d45c0adcba33': {
+ frk_tt_68d28f68f117d45c0adcba33: {
name: 'Legal Proof of Company Registration',
description: `Upload official proof of legal entity registration—e.g., a certificate of incorporation or governmen...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_68cc39efd7916e240451cae5': {
+ frk_tt_68cc39efd7916e240451cae5: {
name: 'AI Risk Assessment Execution (Log)',
description: `Add AI related Risks to the risk register....`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68dc1a3a9b92bb4ffb89e334': {
+ frk_tt_68dc1a3a9b92bb4ffb89e334: {
name: 'Systems Description',
description: `Provide a short paragraph giving a description of your app & any architecture around it.
@@ -512,101 +512,101 @@ F...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68e1d5667f2b14a9b0c2daf8': {
+ frk_tt_68e1d5667f2b14a9b0c2daf8: {
name: 'NEN 7510 Risk Assessments',
description: `Create and assess risks, in the risk register tab, that identifies and evaluates information‑securit...`,
department: 'hr',
frequency: 'yearly',
},
- 'frk_tt_68e1d619944625cc1876540c': {
+ frk_tt_68e1d619944625cc1876540c: {
name: 'Management Review Minutes',
description: `Prove that top management reviews the performance of the ISMS - including incidents, risks, audits, ...`,
department: 'admin',
frequency: 'quarterly',
},
- 'frk_tt_68e52b274a7c38c62db08e80': {
+ frk_tt_68e52b274a7c38c62db08e80: {
name: 'Organisation Chart',
description: `Upload a hierarchical organization chart showing reporting lines, with each box including the person...`,
department: 'hr',
frequency: 'yearly',
},
- 'frk_tt_684069a3a0dd8322b2ac3f03': {
+ frk_tt_684069a3a0dd8322b2ac3f03: {
name: 'Employee Descriptions',
description: `We need to make sure every employee has a clear job description. Once a year, you'll meet with each ...`,
department: 'hr',
frequency: 'yearly',
},
- 'frk_tt_68406cd9dde2d8cd4c463fe0': {
+ frk_tt_68406cd9dde2d8cd4c463fe0: {
name: '2FA',
description: `Always enable 2FA/MFA (Two-Factor Authentication/Multi-Factor Authentication) on Google Workspace, t...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68e80544d9734e0402cfa807': {
+ frk_tt_68e80544d9734e0402cfa807: {
name: 'Role-based Access Controls',
description: `Upload proof that access is managed by roles- RBAC matrix and role definitions or use our template:
...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68e52b27c4bdbf1b24051b8b': {
+ frk_tt_68e52b27c4bdbf1b24051b8b: {
name: 'Employee Performance Evaluations',
description: `Employee Performance Evaluations
Upload one recent employee performance evaluation (anonymized) - ei...`,
department: 'hr',
frequency: 'yearly',
},
- 'frk_tt_68406ca292d9fffb264991b9': {
+ frk_tt_68406ca292d9fffb264991b9: {
name: 'Employee Access',
description: `Ensure you are using an identity provider like Google Workspace and upload a screenshot of a list of...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68e80545a8b432bc59eb8037': {
+ frk_tt_68e80545a8b432bc59eb8037: {
name: 'Incident Response Tabletop Exercise',
description: `Upload evidence of a recent incident response tabletop exercise - agenda/scenario, attendee list and...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68e805457c2dcc784e72e3cc': {
+ frk_tt_68e805457c2dcc784e72e3cc: {
name: 'Access Review Log',
description: `Upload an access review log for key systems -showing review dates, reviewers/owners, users/roles rev...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68e52b269db179c434734766': {
+ frk_tt_68e52b269db179c434734766: {
name: 'Backup Restoration Test',
description: `Provide evidence of a recent backup restoration test - either share a link to a short screen recordi...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_68e52b26b166e2c0a0d11956': {
+ frk_tt_68e52b26b166e2c0a0d11956: {
name: 'Backup logs',
description: `Upload backup job logs covering the last 10 consecutive days (all critical systems) - e.g., exported...`,
department: 'it',
frequency: 'yearly',
},
- 'frk_tt_6901e040a21d5e8fdc9736e8': {
+ frk_tt_6901e040a21d5e8fdc9736e8: {
name: 'Building / Workplace Rules',
description: `Email or note from your building contact (landlord, building manager, or coworking operator) that in...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_6901e041bb02b41fa3b7dca9': {
+ frk_tt_6901e041bb02b41fa3b7dca9: {
name: 'Office Access & Door Monitoring',
description: `If you use a smart lock/alarm system:
- Access list (export or simple table): name, role, code/badge...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_6901e0aa49fb834934748c93': {
+ frk_tt_6901e0aa49fb834934748c93: {
name: 'Visitor Control',
description: `Upload a Visitor log sample for a recent week (paper scan or form export).
Photo of visitor badge/st...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_6901e0aa6d3f2bbab1ea5b84': {
+ frk_tt_6901e0aa6d3f2bbab1ea5b84: {
name: 'Secure Storage',
description: `Physical storage (cabinet/media):
- Photo of locked cabinet
@@ -615,13 +615,13 @@ Photo of visitor badge/st...`,
department: 'admin',
frequency: 'yearly',
},
- 'frk_tt_69033a6bfeb4759be36257bc': {
+ frk_tt_69033a6bfeb4759be36257bc: {
name: 'Infrastructure Inventory',
description: `Upload an up-to-date inventory of infrastructure assets—cloud accounts/resources (compute, DBs, stor...`,
department: 'itsm',
frequency: 'yearly',
},
- 'frk_tt_68fa2a852e70f757188f0c39': {
+ frk_tt_68fa2a852e70f757188f0c39: {
name: 'Production Firewall & No-Public-Access Controls',
description: `Upload proof that production hosts enforce a deny-by-default firewall and that production databases ...`,
department: 'itsm',
diff --git a/packages/ui/package.json b/packages/ui/package.json
index a0d698a18..99545d0f9 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -561,4 +561,4 @@
"sideEffects": false,
"type": "module",
"types": "./dist/index.d.ts"
-}
\ No newline at end of file
+}
diff --git a/packages/ui/src/components/badge.tsx b/packages/ui/src/components/badge.tsx
index 16e03042d..97f6202a3 100644
--- a/packages/ui/src/components/badge.tsx
+++ b/packages/ui/src/components/badge.tsx
@@ -17,8 +17,7 @@ const badgeVariants = cva(
marketing:
"flex items-center opacity-80 px-3 font-mono gap-2 whitespace-nowrap border border bg-primary/10 text-primary hover:bg-primary/5 before:content-[''] before:absolute before:left-0 before:top-0 before:bottom-0 before:w-0.5 before:bg-primary",
warning: 'border-transparent bg-warning text-white hover:bg-warning/80',
- success:
- 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
+ success: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
},
},
defaultVariants: {
diff --git a/packages/ui/src/components/editor/extensions.ts b/packages/ui/src/components/editor/extensions.ts
index a030a9ef0..6339a5d74 100644
--- a/packages/ui/src/components/editor/extensions.ts
+++ b/packages/ui/src/components/editor/extensions.ts
@@ -1,3 +1,4 @@
+import { Extension } from '@tiptap/core';
import CharacterCount from '@tiptap/extension-character-count';
import Highlight from '@tiptap/extension-highlight';
import HorizontalRule from '@tiptap/extension-horizontal-rule';
@@ -13,10 +14,79 @@ import TaskList from '@tiptap/extension-task-list';
import TextAlign from '@tiptap/extension-text-align';
import Typography from '@tiptap/extension-typography';
import Underline from '@tiptap/extension-underline';
+import { Plugin, PluginKey } from '@tiptap/pm/state';
import StarterKit from '@tiptap/starter-kit';
export * from './extensions/file-attachment';
export * from './extensions/mention';
+/**
+ * Custom extension to preserve empty lines when pasting plain text.
+ * TipTap's default behavior collapses consecutive newlines into a single paragraph break.
+ */
+const PreserveNewlines = Extension.create({
+ name: 'preserveNewlines',
+
+ addProseMirrorPlugins() {
+ return [
+ new Plugin({
+ key: new PluginKey('preserveNewlines'),
+ props: {
+ handlePaste: (view, event) => {
+ const clipboardData = event.clipboardData;
+ if (!clipboardData) return false;
+
+ // Only handle plain text paste (not HTML)
+ const html = clipboardData.getData('text/html');
+ if (html) return false;
+
+ const text = clipboardData.getData('text/plain');
+ if (!text) return false;
+
+ // Check if text has multiple consecutive newlines (empty lines)
+ if (!text.includes('\n\n')) return false;
+
+ // Split by double newlines to preserve empty paragraphs
+ const paragraphs = text.split(/\n\n+/);
+ const { schema } = view.state;
+
+ // Ensure required node types exist
+ const paragraphType = schema.nodes.paragraph;
+ const docType = schema.nodes.doc;
+ const hardBreakType = schema.nodes.hardBreak;
+ if (!paragraphType || !docType) return false;
+
+ const nodes = paragraphs.map((para) => {
+ const lines = para.split('\n');
+ const content: ReturnType[] = [];
+
+ lines.forEach((line, lineIndex) => {
+ if (line) {
+ content.push(schema.text(line));
+ }
+ // Add hard break between lines (not after the last line)
+ if (lineIndex < lines.length - 1 && hardBreakType) {
+ content.push(hardBreakType.create());
+ }
+ });
+
+ // Create paragraph with content, or empty paragraph
+ return paragraphType.create(null, content.length > 0 ? content : null);
+ });
+
+ const fragment = docType.create(null, nodes);
+ const slice = fragment.slice(0, fragment.content.size);
+
+ const tr = view.state.tr.replaceSelection(slice);
+ view.dispatch(tr);
+
+ return true;
+ },
+ },
+ }),
+ ];
+ },
+});
+
type DefaultExtensionsOptions = {
placeholder?: string;
openLinksOnClick?: boolean;
@@ -26,6 +96,7 @@ export const defaultExtensions = ({
placeholder = 'Start writing...',
openLinksOnClick = false,
}: DefaultExtensionsOptions = {}) => [
+ PreserveNewlines,
StarterKit.configure({
bulletList: {
HTMLAttributes: {
diff --git a/packages/ui/src/components/editor/index.tsx b/packages/ui/src/components/editor/index.tsx
index 2b8896275..a6afb860d 100644
--- a/packages/ui/src/components/editor/index.tsx
+++ b/packages/ui/src/components/editor/index.tsx
@@ -58,7 +58,7 @@ export const Editor = ({
immediatelyRender: false,
editorProps: {
attributes: {
- class: `prose prose-lg prose-headings:font-title font-default focus:outline-hidden max-w-full ${className || ''}`,
+ class: `prose prose-lg dark:prose-invert prose-headings:font-title font-default focus:outline-hidden max-w-full ${className || ''}`,
},
},
onUpdate: ({ editor }) => {
diff --git a/packages/ui/src/editor.css b/packages/ui/src/editor.css
index 9f6a83fef..c205dca9b 100644
--- a/packages/ui/src/editor.css
+++ b/packages/ui/src/editor.css
@@ -66,49 +66,49 @@ pre {
/* Base editor styles */
.ProseMirror {
- @reference w-full text-sm leading-normal;
+ @apply w-full text-sm leading-normal;
height: var(--editor-height, 100%);
min-height: var(--editor-min-height, 350px);
}
.ProseMirror:focus {
- @reference outline-hidden;
+ @apply outline-hidden;
}
/* Typography */
.ProseMirror h1 {
- @reference text-xl font-semibold text-foreground;
+ @apply text-foreground text-xl font-semibold;
}
.ProseMirror h2 {
- @reference text-lg font-semibold text-foreground;
+ @apply text-foreground text-lg font-semibold;
}
.ProseMirror h3 {
- @reference text-base font-semibold text-foreground;
+ @apply text-foreground text-base font-semibold;
}
.ProseMirror h4 {
- @reference text-sm font-semibold text-foreground;
+ @apply text-foreground text-sm font-semibold;
}
.ProseMirror p {
- @reference text-sm;
+ @apply text-sm;
}
/* Lists */
.ProseMirror ul,
.ProseMirror ol {
- @reference my-1 ml-2 space-y-2;
+ @apply my-1 ml-2 space-y-2;
}
.ProseMirror li {
- @reference text-sm leading-6;
+ @apply text-sm leading-6;
margin: 0 !important;
}
.ProseMirror li > p {
- @reference m-0;
+ @apply m-0;
}
/* Numbered lists specific styling */
@@ -135,35 +135,35 @@ pre {
.ProseMirror h2 + p,
.ProseMirror h3 + p,
.ProseMirror h4 + p {
- @reference mt-1;
+ @apply mt-1;
}
/* Code blocks */
.ProseMirror pre {
- @reference bg-muted p-4 my-4 overflow-x-auto;
+ @apply bg-muted my-4 overflow-x-auto p-4;
}
.ProseMirror code {
- @reference font-mono text-sm;
+ @apply font-mono text-sm;
}
/* Blockquotes */
.ProseMirror blockquote {
- @reference border-l-4 border-primary pl-4 italic my-4;
+ @apply border-primary my-4 border-l-4 pl-4 italic;
}
/* Task lists */
.ProseMirror ul[data-type='taskList'] {
- @reference list-none p-0;
+ @apply list-none p-0;
}
.ProseMirror ul[data-type='taskList'] li {
- @reference flex items-start gap-2;
+ @apply flex items-start gap-2;
}
/* Placeholder */
.ProseMirror p.is-empty::before {
- @reference text-muted-foreground;
+ @apply text-muted-foreground;
content: attr(data-placeholder);
float: left;
height: 0;
@@ -171,9 +171,9 @@ pre {
}
.ProseMirror table {
- @reference w-full border-collapse my-4 border border-border;
+ @apply border-border my-4 w-full border-collapse border;
}
.ProseMirror th {
- @reference text-sm font-medium;
+ @apply text-sm font-medium;
}