From 8d8117bdfa7e4697905b8f4bf673d3f7d0283b61 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 11:17:57 -0500 Subject: [PATCH 01/21] feat(app): integrate @trycompai/design-system and update ESLint configuration --- apps/app/agents.md | 161 +++++++++++++ apps/app/eslint.config.mjs | 34 +++ apps/app/next.config.ts | 3 +- apps/app/package.json | 3 +- .../[orgId]/components/AppShellRailNav.tsx | 96 ++++++++ .../[orgId]/components/AppShellWrapper.tsx | 76 ++++++ .../(app)/[orgId]/components/AppSidebar.tsx | 223 ++++++++++++++++++ apps/app/src/app/(app)/[orgId]/layout.tsx | 83 +++++-- apps/app/src/app/layout.tsx | 2 +- .../components/sidebar-collapse-button.tsx | 13 +- apps/app/src/components/sidebar.tsx | 2 +- apps/portal/eslint.config.mjs | 33 +++ apps/portal/package.json | 2 +- bun.lock | 191 ++++++++++++++- packages/ui/package.json | 2 +- packages/ui/src/components/badge.tsx | 3 +- 16 files changed, 885 insertions(+), 42 deletions(-) create mode 100644 apps/app/agents.md create mode 100644 apps/app/eslint.config.mjs create mode 100644 apps/app/src/app/(app)/[orgId]/components/AppShellRailNav.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx create mode 100644 apps/portal/eslint.config.mjs diff --git a/apps/app/agents.md b/apps/app/agents.md new file mode 100644 index 000000000..f10397340 --- /dev/null +++ b/apps/app/agents.md @@ -0,0 +1,161 @@ +# UI Component Usage Rules (Design System) + +## Core Principle + +**Components do NOT accept `className`. Use variants and props only.** + +This design system enforces strict styling through `class-variance-authority` (cva). +The `className` prop has been removed from all components to prevent style overrides. + +## ❌ These Will NOT Compile + +```tsx +// className is not a valid prop - TypeScript will error + +Content +Status +Content +``` + +## ✅ ALWAYS Do This + +```tsx +// Use component variants + + +Status + +// Use component props +Content +Title +Section Title +Content +``` + +## Layout & Positioning + +For layout concerns (width, grid positioning, margins), use wrapper elements: + +```tsx +// ✅ Wrapper div for layout +
+ +
+ +// ✅ Grid/flex positioning with wrapper +
+ Spanning Card +
+ +// ✅ Use Stack/Grid for spacing + + + + +``` + +## Available Components & Their APIs + +### Layout Primitives + +```tsx +// Stack - flex layout + + {children} + + +// Grid - responsive grid + + {children} + + +// Container - max-width wrapper + + {children} + + +// PageLayout - full page structure + + {children} + +``` + +### Typography + +```tsx +// Heading - h1-h6 with consistent styles +Page Title +Subtitle + +// Text - body text +Description +Important text +``` + +### Interactive + +```tsx +// Button variants: default, outline, secondary, ghost, destructive, link +// Button sizes: default, xs, sm, lg, icon, icon-xs, icon-sm, icon-lg + + + + +// Badge variants: default, secondary, destructive, outline +Active +``` + +### Layout Components + +```tsx +// Card with maxWidth control +Content + +// Section with title/description +
+ + Settings + Manage your preferences + + {children} +
+``` + +## If a Variant Doesn't Exist + +1. **Check the component file** - it might exist and you missed it +2. **Add a new variant** to the component's `cva` definition +3. **Create a new component** if it's a genuinely new pattern + +```tsx +// Example: Adding a variant to button.tsx +const buttonVariants = cva('...base classes...', { + variants: { + variant: { + // existing variants... + newVariant: 'bg-teal-500 text-white hover:bg-teal-600', // ADD HERE + }, + }, +}); +``` + +**NEVER use wrapper divs to apply styles that should be component variants.** + +## Import Pattern + +```tsx +import { + Button, + Card, + CardHeader, + CardContent, + Stack, + Heading, + Text, + Badge, + // ... etc +} from '@trycompai/design-system'; +``` + diff --git a/apps/app/eslint.config.mjs b/apps/app/eslint.config.mjs new file mode 100644 index 000000000..377010027 --- /dev/null +++ b/apps/app/eslint.config.mjs @@ -0,0 +1,34 @@ +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/**', + '**/playwright-report/**', + '**/.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/app/next.config.ts b/apps/app/next.config.ts index 4cb4b03ef..bc9f4839e 100644 --- a/apps/app/next.config.ts +++ b/apps/app/next.config.ts @@ -18,6 +18,7 @@ const config: NextConfig = { }, }, }, + webpack: (config, { isServer }) => { if (isServer) { // Very important, DO NOT REMOVE, it's needed for Prisma to work in the server bundle @@ -40,7 +41,7 @@ const config: NextConfig = { ? `${process.env.STATIC_ASSETS_URL}/app` : '', reactStrictMode: false, - transpilePackages: ['@trycompai/db', '@prisma/client'], + transpilePackages: ['@trycompai/db', '@prisma/client', '@trycompai/design-system'], images: { remotePatterns: [ { diff --git a/apps/app/package.json b/apps/app/package.json index 6e64cba08..8e5589d79 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -57,6 +57,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", + "@trycompai/design-system": "^1.0.2", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", @@ -170,7 +171,7 @@ "db:migrate": "cd ../../packages/db && bunx prisma migrate dev && cd ../../apps/app", "deploy:trigger-prod": "npx trigger.dev@4.0.6 deploy", "dev": "bun i && bunx concurrently --kill-others --names \"next,trigger\" --prefix-colors \"yellow,blue\" \"next dev --turbo -p 3000\" \"bunx trigger.dev@4.0.6 dev\"", - "lint": "next lint && prettier --check .", + "lint": "eslint . && prettier --check .", "prebuild": "bun run db:generate", "postinstall": "prisma generate --schema=./prisma/schema.prisma || exit 0", "start": "next start", diff --git a/apps/app/src/app/(app)/[orgId]/components/AppShellRailNav.tsx b/apps/app/src/app/(app)/[orgId]/components/AppShellRailNav.tsx new file mode 100644 index 000000000..306a90847 --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/components/AppShellRailNav.tsx @@ -0,0 +1,96 @@ +'use client'; + +import { AppShellRailItem } from '@trycompai/design-system'; +import { + FlaskConical, + Gauge, + ListCheck, + NotebookText, + Settings, + Store, + Users, + Zap, +} from 'lucide-react'; +import { usePathname, useRouter } from 'next/navigation'; + +interface AppShellRailNavProps { + organizationId: string; +} + +export function AppShellRailNav({ organizationId }: AppShellRailNavProps) { + const router = useRouter(); + const pathname = usePathname() ?? ''; + + const orgBase = `/${organizationId}`; + + const isActivePrefix = (prefix: string): boolean => { + return pathname === prefix || pathname.startsWith(`${prefix}/`); + }; + + const items = [ + { + href: `${orgBase}/frameworks`, + label: 'Overview', + icon: , + isActive: isActivePrefix(`${orgBase}/frameworks`), + }, + { + href: `${orgBase}/policies`, + label: 'Policies', + icon: , + isActive: isActivePrefix(`${orgBase}/policies`), + }, + { + href: `${orgBase}/tasks`, + label: 'Evidence', + icon: , + isActive: isActivePrefix(`${orgBase}/tasks`), + }, + { + href: `${orgBase}/people/all`, + label: 'People', + icon: , + isActive: isActivePrefix(`${orgBase}/people`), + }, + { + href: `${orgBase}/vendors`, + label: 'Vendors', + icon: , + isActive: isActivePrefix(`${orgBase}/vendors`), + }, + { + href: `${orgBase}/integrations`, + label: 'Integrations', + icon: , + isActive: isActivePrefix(`${orgBase}/integrations`), + }, + { + href: `${orgBase}/cloud-tests`, + label: 'Cloud Tests', + icon: , + isActive: isActivePrefix(`${orgBase}/cloud-tests`), + }, + { + href: `${orgBase}/settings`, + label: 'Settings', + icon: , + isActive: isActivePrefix(`${orgBase}/settings`), + }, + ] as const; + + return ( + <> + {items.map((item) => ( + router.push(item.href)} + /> + ))} + + ); +} + diff --git a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx new file mode 100644 index 000000000..3f293fcd2 --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-dialog'; +import { Header } from '@/components/header'; +import { AssistantSheet } from '@/components/sheets/assistant-sheet'; +import { SidebarProvider } from '@/context/sidebar-context'; +import type { Onboarding, Organization } from '@db'; +import { + AppShell, + AppShellBody, + AppShellContent, + AppShellMain, + AppShellSidebar, +} from '@trycompai/design-system'; +import { Suspense } from 'react'; +import { AppSidebar } from './AppSidebar'; +import { ConditionalOnboardingTracker } from './ConditionalOnboardingTracker'; + +interface AppShellWrapperProps { + children: React.ReactNode; + organization: Organization; + organizations: Organization[]; + logoUrls: Record; + onboarding: Onboarding | null; + isCollapsed: boolean; + isQuestionnaireEnabled: boolean; + isTrustNdaEnabled: boolean; + hasAuditorRole: boolean; + isOnlyAuditor: boolean; +} + +export function AppShellWrapper({ + children, + organization, + organizations, + logoUrls, + onboarding, + isCollapsed, + isQuestionnaireEnabled, + isTrustNdaEnabled, + hasAuditorRole, + isOnlyAuditor, +}: AppShellWrapperProps) { + return ( + + + + + + + + + + {onboarding?.triggerJobId && } +
+ {children} + + + + + + + + + + + ); +} diff --git a/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx new file mode 100644 index 000000000..eb883ec5a --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx @@ -0,0 +1,223 @@ +'use client'; + +import { OrganizationSwitcher } from '@/components/organization-switcher'; +import { SidebarLogo } from '@/components/sidebar-logo'; +import { useSidebar } from '@/context/sidebar-context'; +import type { Organization } from '@db'; +import { + AppShellNav, + AppShellNavFooter, + AppShellNavItem, + AppShellSidebarHeader, + useAppShell, +} from '@trycompai/design-system'; +import { + ClipboardCheck, + FileTextIcon, + FlaskConical, + Gauge, + ListCheck, + NotebookText, + Settings, + ShieldCheck, + ShieldEllipsis, + SidebarClose, + Store, + Users, + Zap, +} from 'lucide-react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; + +// Risk icon from @comp/ui/icons - inline it to avoid extra dependency +const RiskIcon = ({ className }: { className?: string }) => ( + + + + + +); + +interface NavItem { + id: string; + path: string; + name: string; + icon: React.ReactNode; + hidden?: boolean; +} + +interface AppSidebarProps { + organization: Organization; + organizations: Organization[]; + logoUrls: Record; + isQuestionnaireEnabled: boolean; + isTrustNdaEnabled: boolean; + hasAuditorRole: boolean; + isOnlyAuditor: boolean; +} + +export function AppSidebar({ + organization, + organizations, + logoUrls, + isQuestionnaireEnabled, + isTrustNdaEnabled, + hasAuditorRole, + isOnlyAuditor, +}: AppSidebarProps) { + const pathname = usePathname(); + const { isCollapsed } = useSidebar(); + const { toggleSidebar } = useAppShell(); + + const navItems: NavItem[] = [ + { + id: 'frameworks', + path: `/${organization.id}/frameworks`, + name: 'Overview', + icon: , + }, + { + id: 'auditor', + path: `/${organization.id}/auditor`, + name: 'Auditor View', + icon: , + hidden: !hasAuditorRole, + }, + { + id: 'controls', + path: `/${organization.id}/controls`, + name: 'Controls', + icon: , + hidden: !organization.advancedModeEnabled, + }, + { + id: 'policies', + path: `/${organization.id}/policies`, + name: 'Policies', + icon: , + }, + { + id: 'tasks', + path: `/${organization.id}/tasks`, + name: 'Evidence', + icon: , + }, + { + id: 'trust', + path: `/${organization.id}/trust`, + name: 'Trust', + icon: , + hidden: !isTrustNdaEnabled, + }, + { + id: 'people', + path: `/${organization.id}/people/all`, + name: 'People', + icon: , + }, + { + id: 'risk', + path: `/${organization.id}/risk`, + name: 'Risks', + icon: , + }, + { + id: 'vendors', + path: `/${organization.id}/vendors`, + name: 'Vendors', + icon: , + }, + { + id: 'questionnaire', + path: `/${organization.id}/questionnaire`, + name: 'Questionnaire', + icon: , + hidden: !isQuestionnaireEnabled, + }, + { + id: 'integrations', + path: `/${organization.id}/integrations`, + name: 'Integrations', + icon: , + hidden: isOnlyAuditor, + }, + { + id: 'tests', + path: `/${organization.id}/cloud-tests`, + name: 'Cloud Tests', + icon: , + }, + ]; + + const isPathActive = (itemPath: string) => { + const itemPathParts = itemPath.split('/').filter(Boolean); + const itemBaseSegment = itemPathParts.length > 1 ? itemPathParts[1] : ''; + + const currentPathParts = pathname.split('/').filter(Boolean); + const currentBaseSegment = currentPathParts.length > 1 ? currentPathParts[1] : ''; + + if (itemPath === `/${organization.id}` || itemPath === `/${organization.id}/implementation`) { + return ( + pathname === `/${organization.id}` || + pathname?.startsWith(`/${organization.id}/implementation`) + ); + } + + return itemBaseSegment === currentBaseSegment; + }; + + const visibleItems = navItems.filter((item) => !item.hidden); + + return ( + <> + +
+ +
+ +
+
+
+ + + {visibleItems.map((item) => ( + + + {item.name} + + + ))} + + + + {!isOnlyAuditor && ( + + } + > + Settings + + + )} + } onClick={toggleSidebar}> + Collapse sidebar + + + + ); +} diff --git a/apps/app/src/app/(app)/[orgId]/layout.tsx b/apps/app/src/app/(app)/[orgId]/layout.tsx index d14f95951..ba86df56b 100644 --- a/apps/app/src/app/(app)/[orgId]/layout.tsx +++ b/apps/app/src/app/(app)/[orgId]/layout.tsx @@ -1,19 +1,15 @@ -import { AnimatedLayout } from '@/components/animated-layout'; -import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-dialog'; -import { Header } from '@/components/header'; -import { AssistantSheet } from '@/components/sheets/assistant-sheet'; -import { Sidebar } from '@/components/sidebar'; +import { getFeatureFlags } from '@/app/posthog'; +import { APP_AWS_ORG_ASSETS_BUCKET, s3Client } from '@/app/s3'; import { TriggerTokenProvider } from '@/components/trigger-token-provider'; -import { SidebarProvider } from '@/context/sidebar-context'; +import { getOrganizations } from '@/data/getOrganizations'; import { auth } from '@/utils/auth'; +import { GetObjectCommand } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { db, Role } from '@db'; import dynamic from 'next/dynamic'; import { cookies, headers } from 'next/headers'; import { redirect } from 'next/navigation'; -import { Suspense } from 'react'; -import { ConditionalOnboardingTracker } from './components/ConditionalOnboardingTracker'; -import { ConditionalPaddingWrapper } from './components/ConditionalPaddingWrapper'; -import { DynamicMinHeight } from './components/DynamicMinHeight'; +import { AppShellWrapper } from './components/AppShellWrapper'; // Helper to safely parse comma-separated roles string function parseRolesString(rolesStr: string | null | undefined): Role[] { @@ -39,7 +35,7 @@ export default async function Layout({ const cookieStore = await cookies(); const isCollapsed = cookieStore.get('sidebar-collapsed')?.value === 'true'; - let publicAccessToken = cookieStore.get('publicAccessToken')?.value || undefined; + const publicAccessToken = cookieStore.get('publicAccessToken')?.value || undefined; // Get headers once to avoid multiple async calls const requestHeaders = await headers(); @@ -118,25 +114,62 @@ export default async function Layout({ }, }); + // Fetch organizations and feature flags for sidebar + const { organizations } = await getOrganizations(); + + // Generate logo URLs for all organizations + const logoUrls: Record = {}; + if (s3Client && APP_AWS_ORG_ASSETS_BUCKET) { + await Promise.all( + organizations.map(async (org) => { + if (org.logo) { + try { + const command = new GetObjectCommand({ + Bucket: APP_AWS_ORG_ASSETS_BUCKET, + Key: org.logo, + }); + logoUrls[org.id] = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); + } catch { + // Logo not available + } + } + }), + ); + } + + // Check feature flags for menu items + let isQuestionnaireEnabled = false; + let isTrustNdaEnabled = false; + if (session?.user?.id) { + const flags = await getFeatureFlags(session.user.id); + isQuestionnaireEnabled = flags['ai-vendor-questionnaire'] === true; + isTrustNdaEnabled = + flags['is-trust-nda-enabled'] === true || flags['is-trust-nda-enabled'] === 'true'; + } + + // Check auditor role + const hasAuditorRole = roles.includes(Role.auditor); + const isOnlyAuditor = hasAuditorRole && roles.length === 1; + return ( - - } isCollapsed={isCollapsed}> - {onboarding?.triggerJobId && } -
- - {children} - - - - - - - - + + {children} + + ); } diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx index 09a28980a..546eed3b9 100644 --- a/apps/app/src/app/layout.tsx +++ b/apps/app/src/app/layout.tsx @@ -1,5 +1,5 @@ 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/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/portal/eslint.config.mjs b/apps/portal/eslint.config.mjs new file mode 100644 index 000000000..754421ed7 --- /dev/null +++ b/apps/portal/eslint.config.mjs @@ -0,0 +1,33 @@ +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 e1c0c641c..5f5544ee2 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -54,7 +54,7 @@ "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" } diff --git a/bun.lock b/bun.lock index 157ac190b..26e4bdb63 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.20", + "@trycompai/design-system": "^1.0.2", "@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.2", "", { "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-GMnfp/cHTSAYm1wl2zv/LJs2WH+In7fBL1U5RggH2xzwR1+aWuRx8rIPvRL/9ZhIdo5tdm7gGwhNIEnz3Mb9WA=="], + "@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/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: { From d30c833f6330531e98f8871d730dc1a0d31f4968 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 12:10:55 -0500 Subject: [PATCH 02/21] fix(app): update @trycompai/design-system to version 1.0.3 and remove unused files --- ENTERPRISE_API_AUTOMATION_VERSIONING.md | 184 ------------------ apps/app/package.json | 2 +- .../[orgId]/components/AppShellWrapper.tsx | 113 ++++++++++- .../(app)/[orgId]/components/AppSidebar.tsx | 98 +++------- apps/app/src/app/(app)/[orgId]/layout.tsx | 8 + apps/app/src/app/layout.tsx | 1 - apps/app/tailwind.config.ts | 11 -- bun.lock | 4 +- 8 files changed, 146 insertions(+), 275 deletions(-) delete mode 100644 ENTERPRISE_API_AUTOMATION_VERSIONING.md delete mode 100644 apps/app/tailwind.config.ts diff --git a/ENTERPRISE_API_AUTOMATION_VERSIONING.md b/ENTERPRISE_API_AUTOMATION_VERSIONING.md deleted file mode 100644 index 4b0a56c01..000000000 --- a/ENTERPRISE_API_AUTOMATION_VERSIONING.md +++ /dev/null @@ -1,184 +0,0 @@ -# Enterprise API - Automation Versioning Endpoints - -## Overview - -Implement versioning for automation scripts. The Next.js app handles database operations (storing version metadata), while the Enterprise API handles S3 operations (copying/managing script files) and Redis operations (chat history). - -## Context - -### Current S3 Structure - -- **Draft script**: `{orgId}/{taskId}/{automationId}.automation.js` -- Scripts are stored in S3 via the enterprise API - -### New S3 Structure for Versions - -- **Draft script**: `{orgId}/{taskId}/{automationId}.draft.js` -- **Published versions**: `{orgId}/{taskId}/{automationId}.v{version}.js` - -**Migration Note**: Existing scripts at `{automationId}.automation.js` should be moved to `{automationId}.draft.js` - -### Database (handled by Next.js app) - -- `EvidenceAutomationVersion` table stores version metadata -- Next.js app creates version records after enterprise API copies files - -## Endpoints to Implement - -### 1. Publish Draft Script - -**Endpoint**: `POST /api/tasks-automations/publish` - -**Purpose**: Create a new version by copying current draft script to a versioned S3 key. - -**Request Body**: - -```typescript -{ - orgId: string; - taskId: string; - automationId: string; -} -``` - -**Process**: - -1. Construct draft S3 key: `{orgId}/{taskId}/{automationId}.draft.js` -2. Check if draft script exists in S3 -3. If not found, return error: `{ success: false, error: 'No draft script found to publish' }` -4. Query database to get the next version number: - - Find highest existing version for this `automationId` - - Increment by 1 (or start at 1 if no versions exist) -5. Construct version S3 key: `{orgId}/{taskId}/{automationId}.v{nextVersion}.js` -6. Copy draft script to version key in S3 -7. Return success with the version number and scriptKey - -**Response**: - -```typescript -{ - success: boolean; - version?: number; // e.g., 1, 2, 3 - scriptKey?: string; // e.g., "org_xxx/tsk_xxx/aut_xxx.v1.js" - error?: string; -} -``` - -**Note**: Enterprise API determines the version number server-side by querying the database, not from client input. This prevents version conflicts. - -**Error Cases**: - -- Draft script not found in S3 -- S3 copy operation fails -- Invalid orgId/taskId/automationId - ---- - -### 2. Restore Version to Draft - -**Endpoint**: `POST /api/tasks-automations/restore-version` - -**Purpose**: Replace current draft script with a published version's script. Chat history is preserved. - -**Request Body**: - -```typescript -{ - orgId: string; - taskId: string; - automationId: string; - version: number; // Which version to restore (e.g., 1, 2, 3) -} -``` - -**Process**: - -1. Construct version S3 key: `{orgId}/{taskId}/{automationId}.v{version}.js` -2. Check if version script exists in S3 -3. If not found, return error: `{ success: false, error: 'Version not found' }` -4. Construct draft S3 key: `{orgId}/{taskId}/{automationId}.draft.js` -5. Copy version script to draft key in S3 (overwrites current draft) -6. Do NOT touch Redis chat history - it should persist -7. Return success - -**Response**: - -```typescript -{ - success: boolean; - error?: string; -} -``` - -**Error Cases**: - -- Version script not found in S3 -- S3 copy operation fails -- Invalid version number - ---- - -## Implementation Notes - -### S3 Operations - -- Use AWS S3 SDK's `copyObject` method to copy between keys -- Bucket name should come from environment variables -- Ensure proper error handling for S3 operations - -### Authentication - -- These endpoints should require authentication (API key or session) -- Validate that the user has access to the organization/task/automation - -### Redis Chat History - -- **Important**: Do NOT clear or modify chat history when restoring versions -- Chat history key format: `automation:{automationId}:chat` -- Chat history persists regardless of which version is in the draft - -### Example S3 Keys - -For automation `aut_68e6a70803cf925eac17896a` in task `tsk_68e6a5c1e0b762e741c2e020`: - -- **Draft**: `org_68e6a5c1d30338b3981c2104/tsk_68e6a5c1e0b762e741c2e020/aut_68e6a70803cf925eac17896a.draft.js` -- **Version 1**: `org_68e6a5c1d30338b3981c2104/tsk_68e6a5c1e0b762e741c2e020/aut_68e6a70803cf925eac17896a.v1.js` -- **Version 2**: `org_68e6a5c1d30338b3981c2104/tsk_68e6a5c1e0b762e741c2e020/aut_68e6a70803cf925eac17896a.v2.js` - -### Integration Flow - -#### Publishing a Version - -1. User clicks "Publish" in Next.js UI with optional changelog -2. Next.js calls `POST /api/tasks-automations/publish` (no version number in request) -3. Enterprise API: - - Queries database to get next version number - - Copies draft → versioned S3 key - - Returns version number and scriptKey -4. Next.js saves version record to database with returned version number, scriptKey, and changelog - -#### Restoring a Version - -1. User clicks "Restore Version X" in Next.js UI -2. Shows confirmation dialog warning current draft will be lost -3. Next.js calls `POST /api/tasks-automations/restore-version` -4. Enterprise API copies version script → draft S3 key -5. Enterprise API returns success -6. Next.js shows success message -7. User can continue editing in builder with restored script - -### Error Handling - -- Return proper HTTP status codes (404 for not found, 400 for bad request, 500 for S3 errors) -- Include descriptive error messages in response body -- Log errors for debugging - -### Testing Checklist - -- [ ] Can publish a draft script as version 1 -- [ ] Can publish multiple versions (1, 2, 3...) -- [ ] Cannot publish if no draft exists -- [ ] Can restore version 1 to draft -- [ ] Restoring doesn't affect chat history -- [ ] S3 keys follow correct naming convention -- [ ] Proper error messages when scripts don't exist diff --git a/apps/app/package.json b/apps/app/package.json index 8e5589d79..4295cff62 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -57,7 +57,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", - "@trycompai/design-system": "^1.0.2", + "@trycompai/design-system": "^1.0.3", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", diff --git a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx index 3f293fcd2..0cb2885dc 100644 --- a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx +++ b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx @@ -1,17 +1,39 @@ 'use client'; +import { AssistantButton } from '@/components/ai/chat-button'; import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-dialog'; -import { Header } from '@/components/header'; +import { NotificationBell } from '@/components/notifications/notification-bell'; +import { OrganizationSwitcher } from '@/components/organization-switcher'; import { AssistantSheet } from '@/components/sheets/assistant-sheet'; +import { SidebarLogo } from '@/components/sidebar-logo'; +import { SignOut } from '@/components/sign-out'; import { SidebarProvider } from '@/context/sidebar-context'; +import { CertificateCheck } from '@carbon/icons-react'; +import { Avatar, AvatarFallback, AvatarImageNext } from '@comp/ui/avatar'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@comp/ui/dropdown-menu'; import type { Onboarding, Organization } from '@db'; import { AppShell, AppShellBody, AppShellContent, AppShellMain, + AppShellNavbar, + AppShellRail, + AppShellRailItem, AppShellSidebar, + AppShellSidebarHeader, + AppShellUserMenu, + ThemeToggle, } from '@trycompai/design-system'; +import { useTheme } from 'next-themes'; +import Link from 'next/link'; import { Suspense } from 'react'; import { AppSidebar } from './AppSidebar'; import { ConditionalOnboardingTracker } from './ConditionalOnboardingTracker'; @@ -27,6 +49,11 @@ interface AppShellWrapperProps { isTrustNdaEnabled: boolean; hasAuditorRole: boolean; isOnlyAuditor: boolean; + user: { + name: string | null; + email: string; + image: string | null; + }; } export function AppShellWrapper({ @@ -40,17 +67,94 @@ export function AppShellWrapper({ isTrustNdaEnabled, hasAuditorRole, isOnlyAuditor, + user, }: AppShellWrapperProps) { + const { theme, setTheme } = useTheme(); + return ( + + + + + } + centerContent={} + endContent={ + + + + + + {user.image && ( + + )} + + + {user.name?.charAt(0)?.toUpperCase() || + user.email?.charAt(0)?.toUpperCase()} + + + + + + +
+
+ + {user.name} + + + {user.email} + +
+
+
+ + + User Settings + + +
+ Theme + setTheme(isDark ? 'dark' : 'light')} + size="sm" + /> +
+ + +
+
+
+ } + /> + + } + label="Compliance" + /> + - + + {onboarding?.triggerJobId && } -
{children} diff --git a/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx index eb883ec5a..39d4b06f7 100644 --- a/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx +++ b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx @@ -1,52 +1,30 @@ 'use client'; -import { OrganizationSwitcher } from '@/components/organization-switcher'; -import { SidebarLogo } from '@/components/sidebar-logo'; -import { useSidebar } from '@/context/sidebar-context'; +import { + Chemistry, + Dashboard, + Document, + Group, + Integration, + ListChecked, + Policy, + Security, + Settings, + ShoppingBag, + Task, + TaskComplete, + Warning, +} from '@carbon/icons-react'; import type { Organization } from '@db'; import { AppShellNav, AppShellNavFooter, AppShellNavItem, - AppShellSidebarHeader, useAppShell, } from '@trycompai/design-system'; -import { - ClipboardCheck, - FileTextIcon, - FlaskConical, - Gauge, - ListCheck, - NotebookText, - Settings, - ShieldCheck, - ShieldEllipsis, - SidebarClose, - Store, - Users, - Zap, -} from 'lucide-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; -// Risk icon from @comp/ui/icons - inline it to avoid extra dependency -const RiskIcon = ({ className }: { className?: string }) => ( - - - - - -); - interface NavItem { id: string; path: string; @@ -57,8 +35,6 @@ interface NavItem { interface AppSidebarProps { organization: Organization; - organizations: Organization[]; - logoUrls: Record; isQuestionnaireEnabled: boolean; isTrustNdaEnabled: boolean; hasAuditorRole: boolean; @@ -67,15 +43,12 @@ interface AppSidebarProps { export function AppSidebar({ organization, - organizations, - logoUrls, isQuestionnaireEnabled, isTrustNdaEnabled, hasAuditorRole, isOnlyAuditor, }: AppSidebarProps) { const pathname = usePathname(); - const { isCollapsed } = useSidebar(); const { toggleSidebar } = useAppShell(); const navItems: NavItem[] = [ @@ -83,78 +56,78 @@ export function AppSidebar({ id: 'frameworks', path: `/${organization.id}/frameworks`, name: 'Overview', - icon: , + icon: , }, { id: 'auditor', path: `/${organization.id}/auditor`, name: 'Auditor View', - icon: , + icon: , hidden: !hasAuditorRole, }, { id: 'controls', path: `/${organization.id}/controls`, name: 'Controls', - icon: , + icon: , hidden: !organization.advancedModeEnabled, }, { id: 'policies', path: `/${organization.id}/policies`, name: 'Policies', - icon: , + icon: , }, { id: 'tasks', path: `/${organization.id}/tasks`, name: 'Evidence', - icon: , + icon: , }, { id: 'trust', path: `/${organization.id}/trust`, name: 'Trust', - icon: , + icon: , hidden: !isTrustNdaEnabled, }, { id: 'people', path: `/${organization.id}/people/all`, name: 'People', - icon: , + icon: , }, { id: 'risk', path: `/${organization.id}/risk`, name: 'Risks', - icon: , + icon: , }, { id: 'vendors', path: `/${organization.id}/vendors`, name: 'Vendors', - icon: , + icon: , }, { id: 'questionnaire', path: `/${organization.id}/questionnaire`, name: 'Questionnaire', - icon: , + icon: , hidden: !isQuestionnaireEnabled, }, { id: 'integrations', path: `/${organization.id}/integrations`, name: 'Integrations', - icon: , + icon: , hidden: isOnlyAuditor, }, { id: 'tests', path: `/${organization.id}/cloud-tests`, name: 'Cloud Tests', - icon: , + icon: , }, ]; @@ -179,20 +152,6 @@ export function AppSidebar({ return ( <> - -
- -
- -
-
-
- {visibleItems.map((item) => ( @@ -214,9 +173,6 @@ export function AppSidebar({ )} - } onClick={toggleSidebar}> - Collapse sidebar - ); diff --git a/apps/app/src/app/(app)/[orgId]/layout.tsx b/apps/app/src/app/(app)/[orgId]/layout.tsx index ba86df56b..237edeb67 100644 --- a/apps/app/src/app/(app)/[orgId]/layout.tsx +++ b/apps/app/src/app/(app)/[orgId]/layout.tsx @@ -151,6 +151,13 @@ export default async function Layout({ const hasAuditorRole = roles.includes(Role.auditor); const isOnlyAuditor = hasAuditorRole && roles.length === 1; + // User data for navbar + const user = { + name: session.user.name, + email: session.user.email, + image: session.user.image, + }; + return ( {children} diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx index 546eed3b9..3917ea35e 100644 --- a/apps/app/src/app/layout.tsx +++ b/apps/app/src/app/layout.tsx @@ -1,4 +1,3 @@ -import '@/styles/globals.css'; import '@trycompai/design-system/globals.css'; import { LinkedInInsight } from '@/components/tracking/LinkedInInsight'; 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/bun.lock b/bun.lock index 26e4bdb63..d7dfdcbd1 100644 --- a/bun.lock +++ b/bun.lock @@ -211,7 +211,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", - "@trycompai/design-system": "^1.0.2", + "@trycompai/design-system": "^1.0.3", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", @@ -2182,7 +2182,7 @@ "@trycompai/db": ["@trycompai/db@workspace:packages/db"], - "@trycompai/design-system": ["@trycompai/design-system@1.0.2", "", { "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-GMnfp/cHTSAYm1wl2zv/LJs2WH+In7fBL1U5RggH2xzwR1+aWuRx8rIPvRL/9ZhIdo5tdm7gGwhNIEnz3Mb9WA=="], + "@trycompai/design-system": ["@trycompai/design-system@1.0.3", "", { "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-QRVm/bIiQIq2GKqQ5TnNYeoKwgAvQCvwbSu6nqAL4OjsFqA3fTqUh+kb+168cWSn23irqemukTp5XjXVcw6dVQ=="], "@trycompai/email": ["@trycompai/email@workspace:packages/email"], From 43dcaff2d0de343490d90799f9829f814859024b Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 12:46:46 -0500 Subject: [PATCH 03/21] feat(app): enhance AppShellWrapper and AppSidebar with dropdown menus --- .../[orgId]/components/AppShellWrapper.tsx | 98 +++--- .../(app)/[orgId]/components/AppSidebar.tsx | 42 +-- .../src/components/organization-switcher.tsx | 287 +++++------------- 3 files changed, 133 insertions(+), 294 deletions(-) diff --git a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx index 0cb2885dc..fd3044c9a 100644 --- a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx +++ b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx @@ -5,16 +5,14 @@ import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-d import { NotificationBell } from '@/components/notifications/notification-bell'; import { OrganizationSwitcher } from '@/components/organization-switcher'; import { AssistantSheet } from '@/components/sheets/assistant-sheet'; -import { SidebarLogo } from '@/components/sidebar-logo'; -import { SignOut } from '@/components/sign-out'; import { SidebarProvider } from '@/context/sidebar-context'; -import { CertificateCheck } from '@carbon/icons-react'; -import { Avatar, AvatarFallback, AvatarImageNext } from '@comp/ui/avatar'; +import { signOut } from '@/utils/auth-client'; +import { CertificateCheck, Logout, Settings } from '@carbon/icons-react'; import { DropdownMenu, DropdownMenuContent, + DropdownMenuGroup, DropdownMenuItem, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@comp/ui/dropdown-menu'; @@ -30,10 +28,16 @@ import { AppShellSidebar, AppShellSidebarHeader, AppShellUserMenu, + Avatar, + AvatarFallback, + AvatarImage, + LogoIcon, + Text, ThemeToggle, } from '@trycompai/design-system'; import { useTheme } from 'next-themes'; import Link from 'next/link'; +import { usePathname } from 'next/navigation'; import { Suspense } from 'react'; import { AppSidebar } from './AppSidebar'; import { ConditionalOnboardingTracker } from './ConditionalOnboardingTracker'; @@ -70,6 +74,8 @@ export function AppShellWrapper({ user, }: AppShellWrapperProps) { const { theme, setTheme } = useTheme(); + const pathname = usePathname(); + const isSettingsActive = pathname?.startsWith(`/${organization.id}/settings`); return ( @@ -77,13 +83,10 @@ export function AppShellWrapper({ - - + + + + } centerContent={} @@ -91,52 +94,46 @@ export function AppShellWrapper({ - - - {user.image && ( - - )} + + + {user.image && } - - {user.name?.charAt(0)?.toUpperCase() || - user.email?.charAt(0)?.toUpperCase()} - + {user.name?.charAt(0)?.toUpperCase() || user.email?.charAt(0)?.toUpperCase()} - - -
-
- - {user.name} - - - {user.email} - -
-
-
+ +
+ + {user.name} + + + {user.email} + +
- - User Settings - + + + + + Settings + + +
- Theme + Theme setTheme(isDark ? 'dark' : 'light')} - size="sm" />
- + signOut()}> + + Log out +
@@ -145,10 +142,19 @@ export function AppShellWrapper({ } label="Compliance" /> + {!isOnlyAuditor && ( + + } + label="Settings" + /> + + )} diff --git a/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx index 39d4b06f7..e5013b3a6 100644 --- a/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx +++ b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx @@ -9,19 +9,13 @@ import { ListChecked, Policy, Security, - Settings, ShoppingBag, Task, TaskComplete, Warning, } from '@carbon/icons-react'; import type { Organization } from '@db'; -import { - AppShellNav, - AppShellNavFooter, - AppShellNavItem, - useAppShell, -} from '@trycompai/design-system'; +import { AppShellNav, AppShellNavItem } from '@trycompai/design-system'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; @@ -49,7 +43,6 @@ export function AppSidebar({ isOnlyAuditor, }: AppSidebarProps) { const pathname = usePathname(); - const { toggleSidebar } = useAppShell(); const navItems: NavItem[] = [ { @@ -151,29 +144,14 @@ export function AppSidebar({ const visibleItems = navItems.filter((item) => !item.hidden); return ( - <> - - {visibleItems.map((item) => ( - - - {item.name} - - - ))} - - - - {!isOnlyAuditor && ( - - } - > - Settings - - - )} - - + + {visibleItems.map((item) => ( + + + {item.name} + + + ))} + ); } diff --git a/apps/app/src/components/organization-switcher.tsx b/apps/app/src/components/organization-switcher.tsx index 2758cf613..169473fd7 100644 --- a/apps/app/src/components/organization-switcher.tsx +++ b/apps/app/src/components/organization-switcher.tsx @@ -1,26 +1,20 @@ 'use client'; import { changeOrganizationAction } from '@/actions/change-organization'; -import { Button } from '@comp/ui/button'; -import { cn } from '@comp/ui/cn'; import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, -} from '@comp/ui/command'; -import { Dialog, DialogContent, DialogTitle, DialogTrigger } from '@comp/ui/dialog'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@comp/ui/select'; + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@comp/ui/dropdown-menu'; import type { Organization } from '@db'; -import { Check, ChevronsUpDown, Loader2, Plus, Search } from 'lucide-react'; +import { Add, Checkmark, ChevronDown } from '@carbon/icons-react'; import { useAction } from 'next-safe-action/hooks'; -import Image from 'next/image'; import { useRouter } from 'next/navigation'; -import { useQueryState } from 'nuqs'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; interface OrganizationSwitcherProps { organizations: Organization[]; @@ -29,114 +23,39 @@ interface OrganizationSwitcherProps { logoUrls?: Record; } -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 = [ + 'bg-sky-500', + 'bg-blue-500', + 'bg-indigo-500', + 'bg-purple-500', + 'bg-fuchsia-500', + 'bg-pink-500', + 'bg-rose-500', + 'bg-red-500', + 'bg-orange-500', + 'bg-amber-500', + 'bg-yellow-500', + 'bg-lime-500', + 'bg-green-500', + 'bg-emerald-500', + 'bg-teal-500', + 'bg-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 ( -
- {name -
- ); - } - - // 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 = {}, }: 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 sortedOrganizations = [...organizations].sort((a, b) => a.name.localeCompare(b.name)); const { execute, status } = useAction(changeOrganizationAction, { onSuccess: (result) => { @@ -144,7 +63,6 @@ export function OrganizationSwitcher({ if (orgId) { router.push(`/${orgId}/`); } - setIsDialogOpen(false); setPendingOrgId(null); }, onExecute: (args) => { @@ -173,112 +91,49 @@ export function OrganizationSwitcher({ return org.name; }; - const currentOrganization = organization; - const handleOrgChange = (org: Organization) => { - execute({ organizationId: org.id }); + if (org.id !== organization?.id) { + execute({ organizationId: org.id }); + } }; - const handleOpenChange = (open: boolean) => { - setShowOrganizationSwitcher(open); - setIsDialogOpen(open); - }; + const isExecuting = status === 'executing'; return ( -
- - - - - - Select Organization - -
- - -
-
- -
- - No results found - - {sortedOrganizations.map((org) => ( - { - if (org.id !== currentOrganization?.id) { - handleOrgChange(org); - } else { - handleOpenChange(false); - } - }} - disabled={status === 'executing'} - className="flex items-center gap-2" - > - {status === 'executing' && pendingOrgId === org.id ? ( - - ) : currentOrganization?.id === org.id ? ( - - ) : ( -
- )} - - {getDisplayName(org)} - - ))} - - - - { - router.push('/setup?intent=create-additional'); - setIsDialogOpen(false); - }} - disabled={status === 'executing'} - className="flex items-center gap-2" - > - - Create Organization - - - - - -
-
+ + + {organization?.name || 'Select Organization'} + + + + + Organizations + {sortedOrganizations.map((org) => ( + handleOrgChange(org)} + disabled={isExecuting && pendingOrgId === org.id} + > +
+ {getDisplayName(org)} + {organization?.id === org.id && ( + + )} + + ))} + + + router.push('/setup?intent=create-additional')} + disabled={isExecuting} + > + + Create new organization + + + ); } From e2e2e48784cbb8ddc8339ff4a4e0be4f3061c16b Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 13:16:42 -0500 Subject: [PATCH 04/21] feat(app): update @trycompai/design-system to version 1.0.4 and enhance layout components --- apps/app/package.json | 2 +- .../[orgId]/components/AppShellWrapper.tsx | 158 +++++++++++++++++- .../[orgId]/policies/(overview)/page.tsx | 11 +- .../app/(app)/[orgId]/policies/all/page.tsx | 12 +- .../src/app/(app)/[orgId]/policies/layout.tsx | 58 ++++--- bun.lock | 4 +- 6 files changed, 205 insertions(+), 40 deletions(-) diff --git a/apps/app/package.json b/apps/app/package.json index 4295cff62..7e21c9746 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -57,7 +57,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", - "@trycompai/design-system": "^1.0.3", + "@trycompai/design-system": "^1.0.4", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", diff --git a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx index fd3044c9a..b361b0f66 100644 --- a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx +++ b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx @@ -1,13 +1,28 @@ 'use client'; -import { AssistantButton } from '@/components/ai/chat-button'; import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-dialog'; import { NotificationBell } from '@/components/notifications/notification-bell'; import { OrganizationSwitcher } from '@/components/organization-switcher'; import { AssistantSheet } from '@/components/sheets/assistant-sheet'; import { SidebarProvider } from '@/context/sidebar-context'; import { signOut } from '@/utils/auth-client'; -import { CertificateCheck, Logout, Settings } from '@carbon/icons-react'; +import { + CertificateCheck, + Chemistry, + Dashboard, + Document, + Group, + Integration, + ListChecked, + Logout, + Policy, + Security, + Settings, + ShoppingBag, + Task, + TaskComplete, + Warning, +} from '@carbon/icons-react'; import { DropdownMenu, DropdownMenuContent, @@ -17,6 +32,7 @@ import { DropdownMenuTrigger, } from '@comp/ui/dropdown-menu'; import type { Onboarding, Organization } from '@db'; +import type { CommandSearchGroup } from '@trycompai/design-system'; import { AppShell, AppShellBody, @@ -31,13 +47,14 @@ import { Avatar, AvatarFallback, AvatarImage, + CommandSearch, LogoIcon, Text, ThemeToggle, } from '@trycompai/design-system'; import { useTheme } from 'next-themes'; import Link from 'next/link'; -import { usePathname } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; import { Suspense } from 'react'; import { AppSidebar } from './AppSidebar'; import { ConditionalOnboardingTracker } from './ConditionalOnboardingTracker'; @@ -75,11 +92,142 @@ export function AppShellWrapper({ }: AppShellWrapperProps) { const { theme, setTheme } = useTheme(); const pathname = usePathname(); + const router = useRouter(); const isSettingsActive = pathname?.startsWith(`/${organization.id}/settings`); + const searchGroups: CommandSearchGroup[] = [ + { + id: 'navigation', + label: 'Navigation', + items: [ + { + id: 'overview', + label: 'Overview', + icon: , + onSelect: () => router.push(`/${organization.id}/frameworks`), + keywords: ['dashboard', 'home', 'frameworks'], + }, + ...(hasAuditorRole + ? [ + { + id: 'auditor', + label: 'Auditor View', + icon: , + onSelect: () => router.push(`/${organization.id}/auditor`), + keywords: ['audit', 'review'], + }, + ] + : []), + ...(organization.advancedModeEnabled + ? [ + { + id: 'controls', + label: 'Controls', + icon: , + onSelect: () => router.push(`/${organization.id}/controls`), + keywords: ['security', 'compliance'], + }, + ] + : []), + { + id: 'policies', + label: 'Policies', + icon: , + onSelect: () => router.push(`/${organization.id}/policies`), + keywords: ['policy', 'documents'], + }, + { + id: 'evidence', + label: 'Evidence', + icon: , + onSelect: () => router.push(`/${organization.id}/tasks`), + keywords: ['tasks', 'evidence', 'artifacts'], + }, + ...(isTrustNdaEnabled + ? [ + { + id: 'trust', + label: 'Trust', + icon: , + onSelect: () => router.push(`/${organization.id}/trust`), + keywords: ['trust center', 'portal'], + }, + ] + : []), + { + id: 'people', + label: 'People', + icon: , + onSelect: () => router.push(`/${organization.id}/people/all`), + keywords: ['users', 'team', 'members', 'employees'], + }, + { + id: 'risks', + label: 'Risks', + icon: , + onSelect: () => router.push(`/${organization.id}/risk`), + keywords: ['risk management', 'assessment'], + }, + { + id: 'vendors', + label: 'Vendors', + icon: , + onSelect: () => router.push(`/${organization.id}/vendors`), + keywords: ['suppliers', 'third party'], + }, + ...(isQuestionnaireEnabled + ? [ + { + id: 'questionnaire', + label: 'Questionnaire', + icon: , + onSelect: () => router.push(`/${organization.id}/questionnaire`), + keywords: ['survey', 'questions'], + }, + ] + : []), + ...(!isOnlyAuditor + ? [ + { + id: 'integrations', + label: 'Integrations', + icon: , + onSelect: () => router.push(`/${organization.id}/integrations`), + keywords: ['connect', 'apps', 'services'], + }, + ] + : []), + { + id: 'cloud-tests', + label: 'Cloud Tests', + icon: , + onSelect: () => router.push(`/${organization.id}/cloud-tests`), + keywords: ['testing', 'cloud', 'infrastructure'], + }, + ], + }, + ...(!isOnlyAuditor + ? [ + { + id: 'settings', + label: 'Settings', + items: [ + { + id: 'settings-general', + label: 'General Settings', + icon: , + onSelect: () => router.push(`/${organization.id}/settings`), + keywords: ['preferences', 'configuration'], + }, + ], + }, + ] + : []), + ]; + return ( - + @@ -89,7 +237,7 @@ export function AppShellWrapper({ } - centerContent={} + centerContent={} endContent={ diff --git a/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx b/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx index 6e70e7573..73b004b7b 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx @@ -1,3 +1,4 @@ +import { Grid } from '@trycompai/design-system'; import { auth } from '@/utils/auth'; import { db } from '@db'; import type { Metadata } from 'next'; @@ -12,12 +13,10 @@ export default async function PoliciesOverview() { return ( }> -
-
- - -
-
+ + + +
); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx b/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx index 9869bb70e..890769f31 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx @@ -1,4 +1,4 @@ -import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb'; +import { Stack } from '@trycompai/design-system'; import { getValidFilters } from '@/lib/data-table'; import type { SearchParams } from '@/types'; import { db } from '@db'; @@ -31,12 +31,12 @@ export default async function PoliciesPage({ params, searchParams }: PolicyTable }); return ( - } - > + +
+ +
-
+ ); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/layout.tsx b/apps/app/src/app/(app)/[orgId]/policies/layout.tsx index 2bc06dc08..6b55d3ff3 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/layout.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/layout.tsx @@ -1,29 +1,47 @@ -import { SecondaryMenu } from '@comp/ui/secondary-menu'; +'use client'; + +import { PageHeader, PageLayout, Tabs, TabsList, TabsTrigger } from '@trycompai/design-system'; +import { useParams, usePathname, useRouter } from 'next/navigation'; interface LayoutProps { children: React.ReactNode; - params: Promise<{ policyId: string; orgId: string }>; } -export default async function Layout({ children, params }: LayoutProps) { - const { orgId } = await params; +export default function Layout({ children }: LayoutProps) { + const params = useParams<{ orgId: string }>(); + const pathname = usePathname(); + const router = useRouter(); + + const orgId = params.orgId; + + // Determine active tab based on pathname + const getActiveTab = () => { + if (pathname?.includes('/policies/all') || pathname?.includes('/policies/pol_')) { + return 'policies'; + } + return 'overview'; + }; + + const handleTabChange = (value: string | number | null) => { + if (value === 'overview') { + router.push(`/${orgId}/policies`); + } else if (value === 'policies') { + router.push(`/${orgId}/policies/all`); + } + }; return ( -
- -
{children}
-
+ + + + + + Overview + Policies + + + + {children} + ); } diff --git a/bun.lock b/bun.lock index d7dfdcbd1..8118f4c88 100644 --- a/bun.lock +++ b/bun.lock @@ -211,7 +211,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", - "@trycompai/design-system": "^1.0.3", + "@trycompai/design-system": "^1.0.4", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", @@ -2182,7 +2182,7 @@ "@trycompai/db": ["@trycompai/db@workspace:packages/db"], - "@trycompai/design-system": ["@trycompai/design-system@1.0.3", "", { "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-QRVm/bIiQIq2GKqQ5TnNYeoKwgAvQCvwbSu6nqAL4OjsFqA3fTqUh+kb+168cWSn23irqemukTp5XjXVcw6dVQ=="], + "@trycompai/design-system": ["@trycompai/design-system@1.0.4", "", { "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-ImzhZI9UR9S54Q+mm8bJIPWmLG/Br+GB0n/dtu6peQJN3ebk03+hPps16uf0Um5ePuqDXnbwGgaqnmKjMMjlTg=="], "@trycompai/email": ["@trycompai/email@workspace:packages/email"], From acdce7a09cccb2596e4362ef1fbd0b3627bce76b Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Mon, 12 Jan 2026 13:17:00 -0500 Subject: [PATCH 05/21] refactor(risk): remove unused layout and loading components, update page structure --- .../[orgId]/risk/(overview)/RisksTable.tsx | 390 ++++++++++-------- .../(app)/[orgId]/risk/(overview)/loading.tsx | 9 - .../(app)/[orgId]/risk/(overview)/page.tsx | 15 +- .../app/src/app/(app)/[orgId]/risk/layout.tsx | 3 - 4 files changed, 231 insertions(+), 186 deletions(-) delete mode 100644 apps/app/src/app/(app)/[orgId]/risk/(overview)/loading.tsx delete mode 100644 apps/app/src/app/(app)/[orgId]/risk/layout.tsx diff --git a/apps/app/src/app/(app)/[orgId]/risk/(overview)/RisksTable.tsx b/apps/app/src/app/(app)/[orgId]/risk/(overview)/RisksTable.tsx index 08d506e38..622ac6afe 100644 --- a/apps/app/src/app/(app)/[orgId]/risk/(overview)/RisksTable.tsx +++ b/apps/app/src/app/(app)/[orgId]/risk/(overview)/RisksTable.tsx @@ -1,14 +1,26 @@ 'use client'; -import { DataTable } from '@/components/data-table/data-table'; -import { DataTableToolbar } from '@/components/data-table/data-table-toolbar'; import { CreateRiskSheet } from '@/components/sheets/create-risk-sheet'; -import { useDataTable } from '@/hooks/use-data-table'; import { getFiltersStateParser, getSortingStateParser } from '@/lib/parsers'; +import { Add, OverflowMenuHorizontal } from '@carbon/icons-react'; import type { Member, Risk, User } from '@db'; import { Risk as RiskType } from '@db'; -import { ColumnDef } from '@tanstack/react-table'; +import { + Badge, + Button, + HStack, + Spinner, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + Text, +} from '@trycompai/design-system'; import { Loader2 } from 'lucide-react'; +import { useRouter } from 'next/navigation'; import { parseAsArrayOf, parseAsInteger, @@ -22,7 +34,6 @@ import * as z from 'zod/v3'; import { getRisksAction } from './actions/get-risks-action'; import { RiskOnboardingProvider } from './components/risk-onboarding-context'; import { RisksLoadingAnimation } from './components/risks-loading-animation'; -import { columns as getColumns } from './components/table/RiskColumns'; import type { GetRiskSchema } from './data/validations'; import { useOnboardingStatus } from './hooks/use-onboarding-status'; @@ -35,29 +46,78 @@ const ACTIVE_STATUSES: Array<'pending' | 'processing' | 'created' | 'assessing'> 'assessing', ]; +function getSeverityBadge(likelihood: string, impact: string) { + // Calculate severity based on likelihood and impact + const likelihoodScore: Record = { + very_unlikely: 1, + unlikely: 2, + possible: 3, + likely: 4, + very_likely: 5, + }; + const impactScore: Record = { + insignificant: 1, + minor: 2, + moderate: 3, + major: 4, + severe: 5, + }; + + const score = (likelihoodScore[likelihood] || 1) * (impactScore[impact] || 1); + + if (score >= 15) { + return High; + } + if (score >= 8) { + return Medium; + } + return Low; +} + +function getStatusBadge(status: string) { + switch (status) { + case 'open': + return Open; + case 'pending': + return Pending; + case 'closed': + return Resolved; + default: + return {status}; + } +} + +function formatDate(date: Date): string { + return new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(new Date(date)); +} + export const RisksTable = ({ risks: initialRisks, assignees, pageCount: initialPageCount, onboardingRunId, - searchParams: initialSearchParams, orgId, }: { risks: RiskRow[]; assignees: (Member & { user: User })[]; pageCount: number; onboardingRunId?: string | null; - searchParams: GetRiskSchema; + searchParams?: GetRiskSchema; orgId: string; }) => { - const [_, setOpenSheet] = useQueryState('create-risk-sheet'); + const router = useRouter(); + const [, setOpenSheet] = useQueryState('create-risk-sheet'); const { itemStatuses, progress, itemsInfo, isActive, isLoading } = useOnboardingStatus( onboardingRunId, 'risks', ); - // Read current search params from URL (synced with table state via useDataTable) + // Read current search params from URL const [page] = useQueryState('page', parseAsInteger.withDefault(1)); const [perPage] = useQueryState('perPage', parseAsInteger.withDefault(50)); const [title] = useQueryState('title', parseAsString.withDefault('')); @@ -91,7 +151,6 @@ export const RisksTable = ({ // Create stable SWR key from current search params const swrKey = useMemo(() => { if (!orgId) return null; - // Serialize search params to create a stable key const key = JSON.stringify(currentSearchParams); return ['risks', orgId, key] as const; }, [orgId, currentSearchParams]); @@ -103,35 +162,27 @@ export const RisksTable = ({ }, [orgId, currentSearchParams]); // Use SWR to fetch risks with polling for real-time updates - // Poll faster during onboarding, slower otherwise const { data: risksData } = useSWR(swrKey, fetcher, { fallbackData: { data: initialRisks, pageCount: initialPageCount }, - refreshInterval: isActive ? 1000 : 5000, // 1s during onboarding, 5s otherwise + refreshInterval: isActive ? 1000 : 5000, revalidateOnFocus: false, revalidateOnReconnect: true, keepPreviousData: true, }); const risks = risksData?.data || initialRisks; - const pageCount = risksData?.pageCount ?? initialPageCount; - // Check if all risks are done assessing (either completed in metadata or closed in DB) - // Also check if there are any pending/processing risks in metadata that haven't been created yet + // Check if all risks are done assessing const allRisksDoneAssessing = useMemo(() => { - // If no risks exist yet, we're not done if (risks.length === 0) { - // But check if there are risks in metadata that should exist if (itemsInfo.length > 0) return false; return false; } - // Check if we're still creating risks by comparing DB count with expected total - // If progress.total exists and risks.length < progress.total, we're still creating if (progress && risks.length < progress.total) { return false; } - // If there are pending/processing risks in metadata that aren't in DB yet, we're not done const hasPendingRisks = itemsInfo.some((item) => { const status = itemStatuses[item.id]; return ( @@ -145,15 +196,11 @@ export const RisksTable = ({ if (hasPendingRisks) return false; - // Check if all risks in DB are either: - // 1. Completed in metadata (status === 'completed') - // 2. Closed in database (status === 'closed') const allDbRisksDone = risks.every((risk) => { const metadataStatus = itemStatuses[risk.id]; return metadataStatus === 'completed' || risk.status === 'closed'; }); - // Also check if there are any risks in metadata that are still assessing const hasAssessingRisks = Object.values(itemStatuses).some( (status) => status === 'assessing' || status === 'processing', ); @@ -165,12 +212,8 @@ export const RisksTable = ({ const mergedRisks = useMemo(() => { const dbRiskIds = new Set(risks.map((r) => r.id)); - // Mark risks in DB as "assessing" if they're open and onboarding is active - // Don't mark as assessing if risk is already closed (resolved) const risksWithStatus = risks.map((risk) => { const metadataStatus = itemStatuses[risk.id]; - // If risk exists in DB but status is open and onboarding is active, it's being assessed - // Only mark as assessing if status is open (not closed) if (risk.status === 'open' && isActive && onboardingRunId && !metadataStatus) { return { ...risk, isAssessing: true }; } @@ -179,7 +222,6 @@ export const RisksTable = ({ const pendingRisks: RiskRow[] = itemsInfo .filter((item) => { - // Only show items that are pending/processing and not yet in DB const status = itemStatuses[item.id]; return ( (status === 'pending' || status === 'processing') && @@ -187,139 +229,95 @@ export const RisksTable = ({ !item.id.startsWith('temp_') ); }) - .map((item) => { - // Create a placeholder risk row for pending items - const status = itemStatuses[item.id]; - return { - id: item.id, - title: item.name, - description: 'Being researched and created by AI...', - category: 'other' as const, - department: null, - status: 'open' as const, - likelihood: 'very_unlikely' as const, - impact: 'insignificant' as const, - residualLikelihood: 'very_unlikely' as const, - residualImpact: 'insignificant' as const, - treatmentStrategy: 'accept' as const, - treatmentStrategyDescription: null, - organizationId: orgId, - assigneeId: null, - assignee: null, - createdAt: new Date(), - updatedAt: new Date(), - isPending: true, - } as RiskRow; - }); - - // Also handle temp IDs (risks being created) + .map((item) => ({ + id: item.id, + title: item.name, + description: 'Being researched and created by AI...', + category: 'other' as const, + department: null, + status: 'open' as const, + likelihood: 'very_unlikely' as const, + impact: 'insignificant' as const, + residualLikelihood: 'very_unlikely' as const, + residualImpact: 'insignificant' as const, + treatmentStrategy: 'accept' as const, + treatmentStrategyDescription: null, + organizationId: orgId, + assigneeId: null, + assignee: null, + createdAt: new Date(), + updatedAt: new Date(), + isPending: true, + })); + const tempRisks: RiskRow[] = itemsInfo .filter((item) => item.id.startsWith('temp_')) - .map((item) => { - const status = itemStatuses[item.id]; - return { - id: item.id, - title: item.name, - description: 'Being researched and created by AI...', - category: 'other' as const, - department: null, - status: 'open' as const, - likelihood: 'very_unlikely' as const, - impact: 'insignificant' as const, - residualLikelihood: 'very_unlikely' as const, - residualImpact: 'insignificant' as const, - treatmentStrategy: 'accept' as const, - treatmentStrategyDescription: null, - organizationId: orgId, - assigneeId: null, - assignee: null, - createdAt: new Date(), - updatedAt: new Date(), - isPending: true, - } as RiskRow; - }); + .map((item) => ({ + id: item.id, + title: item.name, + description: 'Being researched and created by AI...', + category: 'other' as const, + department: null, + status: 'open' as const, + likelihood: 'very_unlikely' as const, + impact: 'insignificant' as const, + residualLikelihood: 'very_unlikely' as const, + residualImpact: 'insignificant' as const, + treatmentStrategy: 'accept' as const, + treatmentStrategyDescription: null, + organizationId: orgId, + assigneeId: null, + assignee: null, + createdAt: new Date(), + updatedAt: new Date(), + isPending: true, + })); return [...risksWithStatus, ...pendingRisks, ...tempRisks]; }, [risks, itemsInfo, itemStatuses, orgId, isActive, onboardingRunId]); - const columns = useMemo[]>(() => getColumns(orgId), [orgId]); - - const { table } = useDataTable({ - data: mergedRisks, - columns, - pageCount, - getRowId: (row) => row.id, - initialState: { - pagination: { - pageSize: 50, - pageIndex: 0, - }, - sorting: [{ id: 'title', desc: true }], - columnPinning: { right: ['actions'] }, - }, - shallow: false, - clearOnDefault: true, - }); - - const getRowProps = useMemo( - () => (risk: RiskRow) => { - const status = itemStatuses[risk.id] || (risk.isPending ? 'pending' : undefined); - const isAssessing = risk.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], - ); - - // Calculate actual assessment progress + // Calculate assessment progress const assessmentProgress = useMemo(() => { if (!progress || !itemsInfo.length) { return null; } - // Count risks that are completed (either 'completed' in metadata or 'closed' in DB) const completedCount = risks.filter((risk) => { const metadataStatus = itemStatuses[risk.id]; return metadataStatus === 'completed' || risk.status === 'closed'; }).length; - // Also count risks in metadata that are completed but not yet in DB const completedInMetadata = Object.values(itemStatuses).filter( (status) => status === 'completed', ).length; - // Total is the max of progress.total, itemsInfo.length, or actual risks created const total = Math.max(progress.total, itemsInfo.length, risks.length); - - // Completed is the max of DB closed risks or metadata completed const completed = Math.max(completedCount, completedInMetadata); return { total, completed }; }, [progress, itemsInfo, risks, itemStatuses]); + const isRowBlocked = (risk: RiskRow) => { + const status = itemStatuses[risk.id] || (risk.isPending ? 'pending' : undefined); + const isAssessing = risk.isAssessing || status === 'assessing'; + return ( + (status && + ACTIVE_STATUSES.includes(status as 'pending' | 'processing' | 'created' | 'assessing')) || + isAssessing + ); + }; + + const handleRowClick = (riskId: string) => { + router.push(`/${orgId}/risk/${riskId}`); + }; + const isEmpty = mergedRisks.length === 0; - // Show empty state if onboarding is active (even if progress metadata isn't set yet) const showEmptyState = isEmpty && onboardingRunId && isActive; - // 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; } - // Show loading animation instead of table when empty and onboarding is active if (showEmptyState) { return ( <> @@ -332,49 +330,103 @@ export const RisksTable = ({ return ( <> - row.id} - rowClickBasePath={`/${orgId}/risk`} - getRowProps={getRowProps} - > - <> - - {isActive && !allRisksDoneAssessing && ( -
-
- -
-
- - {assessmentProgress - ? assessmentProgress.completed === 0 + + {/* Toolbar */} + + + + + {/* Onboarding Progress Banner */} + {isActive && !allRisksDoneAssessing && ( +
+
+ +
+
+ + {assessmentProgress + ? assessmentProgress.completed === 0 + ? 'Researching and creating risks' + : 'Assessing risks and generating mitigation plans' + : progress + ? progress.completed === 0 ? 'Researching and creating risks' - : assessmentProgress.completed < assessmentProgress.total - ? 'Assessing risks and generating mitigation plans' - : 'Assessing risks and generating mitigation plans' - : progress - ? progress.completed === 0 - ? 'Researching and creating risks' - : 'Assessing risks and generating mitigation plans' - : 'Researching and creating risks'} - - - {assessmentProgress - ? assessmentProgress.completed === 0 + : 'Assessing risks and generating mitigation plans' + : 'Researching and creating risks'} + + + {assessmentProgress + ? assessmentProgress.completed === 0 + ? 'AI is analyzing your organization...' + : `${assessmentProgress.completed}/${assessmentProgress.total} risks assessed` + : progress + ? progress.completed === 0 ? 'AI is analyzing your organization...' - : `${assessmentProgress.completed}/${assessmentProgress.total} risks assessed` - : progress - ? progress.completed === 0 - ? 'AI is analyzing your organization...' - : `${progress.completed}/${progress.total} risks created` - : 'AI is analyzing your organization...'} - -
+ : `${progress.completed}/${progress.total} risks created` + : 'AI is analyzing your organization...'} +
- )} - - +
+ )} + + {/* Table */} + + + + Risk + Severity + Status + Owner + Updated + + Actions + + + + + {mergedRisks.map((risk) => { + const blocked = isRowBlocked(risk); + return ( + !blocked && handleRowClick(risk.id)} + style={{ cursor: blocked ? 'default' : 'pointer' }} + data-state={blocked ? 'disabled' : undefined} + > + + + {blocked && } + {risk.title} + + + {getSeverityBadge(risk.likelihood, risk.impact)} + {getStatusBadge(risk.status)} + + {risk.assignee?.name || 'Unassigned'} + + + {formatDate(risk.updatedAt)} + + +
+ +
+
+
+ ); + })} +
+
+ diff --git a/apps/app/src/app/(app)/[orgId]/risk/(overview)/loading.tsx b/apps/app/src/app/(app)/[orgId]/risk/(overview)/loading.tsx deleted file mode 100644 index 4f38f9a92..000000000 --- a/apps/app/src/app/(app)/[orgId]/risk/(overview)/loading.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import Loader from '@/components/ui/loader'; - -export default function Loading() { - return ( -
- -
- ); -} diff --git a/apps/app/src/app/(app)/[orgId]/risk/(overview)/page.tsx b/apps/app/src/app/(app)/[orgId]/risk/(overview)/page.tsx index fc3b0e8b9..2e164a736 100644 --- a/apps/app/src/app/(app)/[orgId]/risk/(overview)/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/risk/(overview)/page.tsx @@ -1,8 +1,8 @@ import { AppOnboarding } from '@/components/app-onboarding'; -import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb'; import { CreateRiskSheet } from '@/components/sheets/create-risk-sheet'; import { getValidFilters } from '@/lib/data-table'; import { db } from '@db'; +import { PageHeader, PageLayout } from '@trycompai/design-system'; import type { Metadata } from 'next'; import { cache } from 'react'; import { RisksTable } from './RisksTable'; @@ -48,7 +48,11 @@ export default async function RiskRegisterPage(props: { // Show AppOnboarding only if empty, default view, AND onboarding is not active if (isEmpty && isDefaultView && !isOnboardingActive) { return ( -
+ + -
+ ); } return ( - + + - + ); } diff --git a/apps/app/src/app/(app)/[orgId]/risk/layout.tsx b/apps/app/src/app/(app)/[orgId]/risk/layout.tsx deleted file mode 100644 index 983c329e5..000000000 --- a/apps/app/src/app/(app)/[orgId]/risk/layout.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default async function Layout({ children }: { children: React.ReactNode }) { - return
{children}
; -} From e05b0dc85e90197acf7c82f496f3db65874b5c84 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 13:52:42 -0500 Subject: [PATCH 06/21] feat(policies): implement new PolicyTabs and PoliciesTableDS components --- apps/app/agents.md | 56 ++++- .../all/components/PoliciesTableDS.tsx | 218 ++++++++++++++++++ .../app/(app)/[orgId]/policies/all/page.tsx | 38 +-- .../policies/components/PolicyTabs.tsx | 36 +++ .../src/app/(app)/[orgId]/policies/layout.tsx | 39 +--- .../components/sheets/create-policy-sheet.tsx | 23 +- 6 files changed, 341 insertions(+), 69 deletions(-) create mode 100644 apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/policies/components/PolicyTabs.tsx diff --git a/apps/app/agents.md b/apps/app/agents.md index f10397340..558d718f7 100644 --- a/apps/app/agents.md +++ b/apps/app/agents.md @@ -2,11 +2,66 @@ ## Core Principle +**ONLY use components from `@trycompai/design-system`.** Do not use shadcn/ui, Radix primitives, or custom components when a design system component exists. + **Components do NOT accept `className`. Use variants and props only.** This design system enforces strict styling through `class-variance-authority` (cva). The `className` prop has been removed from all components to prevent style overrides. +## Component Priority + +1. **First choice:** `@trycompai/design-system` +2. **Never:** Custom implementations when DS has the component + +```tsx +// ✅ ALWAYS - Use design system +import { Button, Table, Badge, Tabs } from '@trycompai/design-system'; + +// ❌ NEVER - Don't use @comp/ui when DS has the component +import { Button } from '@comp/ui/button'; +import { Table } from '@comp/ui/table'; +``` + +## Server vs Client Components + +**Layouts should be server-side rendered.** Any client-side logic (hooks, state, event handlers) must be wrapped in its own `'use client'` component. + +```tsx +// ✅ Server layout with client component for interactivity +// layout.tsx (server) +import { ClientTabs } from './components/ClientTabs'; + +export default function Layout({ children }) { + return ( + + + {/* Client component for interactive tabs */} + {children} + + ); +} + +// components/ClientTabs.tsx (client) +'use client'; +export function ClientTabs() { + const router = useRouter(); + // ... client logic +} + +// ❌ NEVER - Don't make entire layout a client component +'use client'; +export default function Layout({ children }) { ... } +``` + +## Avoid nuqs + +Don't use `nuqs` for query state. Use standard Next.js patterns: + +- `useRouter().push()` for navigation +- `useSearchParams()` for reading query params +- Server-side `searchParams` prop for initial state + ## ❌ These Will NOT Compile ```tsx @@ -158,4 +213,3 @@ import { // ... etc } from '@trycompai/design-system'; ``` - diff --git a/apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx b/apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx new file mode 100644 index 000000000..65953ea42 --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx @@ -0,0 +1,218 @@ +'use client'; + +import { CreatePolicySheet } from '@/components/sheets/create-policy-sheet'; +import { StatusIndicator } from '@/components/status-indicator'; +import { formatDate } from '@/lib/format'; +import { downloadAllPolicies } from '@/lib/pdf-generator'; +import { Add, Download } from '@carbon/icons-react'; +import { Button } from '@comp/ui/button'; +import type { Policy } from '@db'; +import { + Badge, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + Text, +} from '@trycompai/design-system'; +import { ExternalLink, Loader2 } from 'lucide-react'; +import Link from 'next/link'; +import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { useState } from 'react'; +import { getLogsForPolicy } from '../../[policyId]/data'; +import { usePolicyTailoringStatus, type PolicyTailoringStatus } from './policy-tailoring-context'; + +interface PoliciesTableDSProps { + policies: Policy[]; +} + +const ACTIVE_STATUSES: PolicyTailoringStatus[] = ['queued', 'pending', 'processing']; + +export function PoliciesTableDS({ policies }: PoliciesTableDSProps) { + const params = useParams<{ orgId: string }>(); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const orgId = params.orgId; + const [isDownloadingAll, setIsDownloadingAll] = useState(false); + + const handleDownloadAll = async () => { + setIsDownloadingAll(true); + try { + const logsEntries = await Promise.all( + policies.map(async (policy) => { + const logs = await getLogsForPolicy(policy.id); + return [policy.id, logs] as const; + }), + ); + const policyLogs = Object.fromEntries(logsEntries); + downloadAllPolicies(policies, policyLogs); + } finally { + setIsDownloadingAll(false); + } + }; + + const handleCreatePolicy = () => { + const params = new URLSearchParams(searchParams.toString()); + params.set('create-policy-sheet', 'true'); + router.push(`${pathname}?${params.toString()}`); + }; + + const handleRowClick = (policyId: string) => { + router.push(`/${orgId}/policies/${policyId}`); + }; + + return ( + <> + +
+ {policies.length > 0 && ( + + )} + +
+ + {policies.length === 0 ? ( +
+ No policies found. +
+ ) : ( + + + + Policy Name + Status + Department + Last Updated + + + + {policies.map((policy) => ( + + ))} + +
+ )} +
+ + + + ); +} + +interface PolicyRowProps { + policy: Policy; + orgId: string; + onRowClick: (policyId: string) => void; +} + +function PolicyRow({ policy, orgId, onRowClick }: PolicyRowProps) { + const status = usePolicyTailoringStatus(policy.id); + const isTailoring = status && ACTIVE_STATUSES.includes(status); + + return ( + !isTailoring && onRowClick(policy.id)} + style={{ cursor: isTailoring ? 'not-allowed' : 'pointer' }} + > + + + + + + + + {policy.department} + + + + {formatDate(policy.updatedAt)} + + + + ); +} + +interface PolicyNameCellProps { + policy: Policy; + orgId: string; + isTailoring: boolean | undefined; +} + +function PolicyNameCell({ policy, orgId, isTailoring }: PolicyNameCellProps) { + const policyHref = `/${orgId}/policies/${policy.id}`; + + if (isTailoring) { + return ( +
+ + + {policy.name} + +
+ ); + } + + return ( + e.stopPropagation()} + className="group flex items-center gap-2" + > + + {policy.name} + + + + ); +} + +interface PolicyStatusCellProps { + policy: Policy; + status: PolicyTailoringStatus | undefined; + isTailoring: boolean | undefined; +} + +function PolicyStatusCell({ policy, status, isTailoring }: PolicyStatusCellProps) { + if (isTailoring) { + const label = + status === 'processing' ? 'Tailoring' : status === 'queued' ? 'Queued' : 'Preparing'; + + return ( +
+ + {label} +
+ ); + } + + return ; +} diff --git a/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx b/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx index 890769f31..1ba3d0b41 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx @@ -1,42 +1,24 @@ -import { Stack } from '@trycompai/design-system'; -import { getValidFilters } from '@/lib/data-table'; -import type { SearchParams } from '@/types'; import { db } from '@db'; import type { Metadata } from 'next'; -import { FullPolicyHeaderActions } from './components/FullPolicyHeaderActions'; -import { PoliciesTable } from './components/policies-table'; -import { getPolicies } from './data/queries'; -import { searchParamsCache } from './data/validations'; +import { PoliciesTableDS } from './components/PoliciesTableDS'; +import { PolicyTailoringProvider } from './components/policy-tailoring-context'; interface PolicyTableProps { params: Promise<{ orgId: string }>; - searchParams: Promise; } -export default async function PoliciesPage({ params, searchParams }: PolicyTableProps) { - const [{ orgId }, resolvedSearchParams] = await Promise.all([params, searchParams]); - const search = searchParamsCache.parse(resolvedSearchParams); - const validFilters = getValidFilters(search.filters); +export default async function PoliciesPage({ params }: PolicyTableProps) { + const { orgId } = await params; - const promises = Promise.all([ - getPolicies({ - ...search, - filters: validFilters, - }), - ]); - - const onboarding = await db.onboarding.findFirst({ - where: { organizationId: orgId }, - select: { triggerJobId: true }, + const policies = await db.policy.findMany({ + where: { organizationId: orgId, isArchived: false }, + orderBy: { updatedAt: 'desc' }, }); return ( - -
- -
- -
+ + + ); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/components/PolicyTabs.tsx b/apps/app/src/app/(app)/[orgId]/policies/components/PolicyTabs.tsx new file mode 100644 index 000000000..6ade04c13 --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/policies/components/PolicyTabs.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { Tabs, TabsList, TabsTrigger } from '@trycompai/design-system'; +import { useParams, usePathname, useRouter } from 'next/navigation'; + +export function PolicyTabs() { + const params = useParams<{ orgId: string }>(); + const pathname = usePathname(); + const router = useRouter(); + + const orgId = params.orgId; + + const getActiveTab = () => { + if (pathname?.includes('/policies/all') || pathname?.includes('/policies/pol_')) { + return 'policies'; + } + return 'overview'; + }; + + const handleTabChange = (value: string | number | null) => { + if (value === 'overview') { + router.push(`/${orgId}/policies`); + } else if (value === 'policies') { + router.push(`/${orgId}/policies/all`); + } + }; + + return ( + + + Overview + Policies + + + ); +} diff --git a/apps/app/src/app/(app)/[orgId]/policies/layout.tsx b/apps/app/src/app/(app)/[orgId]/policies/layout.tsx index 6b55d3ff3..e1872e661 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/layout.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/layout.tsx @@ -1,46 +1,15 @@ -'use client'; - -import { PageHeader, PageLayout, Tabs, TabsList, TabsTrigger } from '@trycompai/design-system'; -import { useParams, usePathname, useRouter } from 'next/navigation'; +import { PageHeader, PageLayout } from '@trycompai/design-system'; +import { PolicyTabs } from './components/PolicyTabs'; interface LayoutProps { children: React.ReactNode; } export default function Layout({ children }: LayoutProps) { - const params = useParams<{ orgId: string }>(); - const pathname = usePathname(); - const router = useRouter(); - - const orgId = params.orgId; - - // Determine active tab based on pathname - const getActiveTab = () => { - if (pathname?.includes('/policies/all') || pathname?.includes('/policies/pol_')) { - return 'policies'; - } - return 'overview'; - }; - - const handleTabChange = (value: string | number | null) => { - if (value === 'overview') { - router.push(`/${orgId}/policies`); - } else if (value === 'policies') { - router.push(`/${orgId}/policies/all`); - } - }; - return ( - + - - - - Overview - Policies - - - + {children} ); diff --git a/apps/app/src/components/sheets/create-policy-sheet.tsx b/apps/app/src/components/sheets/create-policy-sheet.tsx index 317383961..7a7f10148 100644 --- a/apps/app/src/components/sheets/create-policy-sheet.tsx +++ b/apps/app/src/components/sheets/create-policy-sheet.tsx @@ -6,16 +6,29 @@ 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 { 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); + }; + + const handleClose = () => { + handleOpenChange(false); }; if (isDesktop) { @@ -28,7 +41,7 @@ export function CreatePolicySheet() { size="icon" variant="ghost" className="m-0 size-auto p-0 hover:bg-transparent" - onClick={() => setOpen(null)} + onClick={handleClose} > From 294398d51ffeaa40806052e8bbea465b9de33d9d Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 14:19:40 -0500 Subject: [PATCH 07/21] feat(policies): simplify layout structure and enhance policy overview page --- .../components/policy-assignee-chart.tsx | 9 +- .../components/policy-status-chart.tsx | 9 +- .../[orgId]/policies/(overview)/page.tsx | 17 +-- .../all/components/PoliciesTableDS.tsx | 116 ++++-------------- .../all/components/PolicyPageActions.tsx | 71 +++++++++++ .../app/(app)/[orgId]/policies/all/page.tsx | 13 +- .../src/app/(app)/[orgId]/policies/layout.tsx | 11 +- 7 files changed, 126 insertions(+), 120 deletions(-) create mode 100644 apps/app/src/app/(app)/[orgId]/policies/all/components/PolicyPageActions.tsx diff --git a/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-assignee-chart.tsx b/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-assignee-chart.tsx index ed01f8f87..80564bd14 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-assignee-chart.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-assignee-chart.tsx @@ -27,11 +27,12 @@ interface PolicyAssigneeChartProps { data?: AssigneeData[] | null; } +// Using oklch values from DS globals.css const CHART_COLORS = { - published: 'hsl(var(--chart-positive))', // green - draft: 'hsl(var(--chart-neutral))', // yellow - archived: 'hsl(var(--chart-warning))', // gray - needs_review: 'hsl(var(--chart-destructive))', // red + published: 'oklch(0.6 0.16 145)', // --success (green) + draft: 'oklch(0.75 0.15 85)', // --warning (yellow) + archived: 'oklch(0.556 0 0)', // --muted-foreground (gray) + needs_review: 'oklch(0.58 0.22 27)', // --destructive (red) }; export function PolicyAssigneeChart({ data }: PolicyAssigneeChartProps) { diff --git a/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-status-chart.tsx b/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-status-chart.tsx index e2bfbd187..49ccd19f6 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-status-chart.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/(overview)/components/policy-status-chart.tsx @@ -25,11 +25,12 @@ interface PolicyStatusChartProps { data?: PolicyOverviewData | null; } +// Using oklch values from DS globals.css const CHART_COLORS = { - published: 'hsl(var(--chart-positive))', // green - draft: 'hsl(var(--chart-neutral))', // yellow - archived: 'hsl(var(--chart-warning))', // gray - needs_review: 'hsl(var(--chart-destructive))', // red + published: 'oklch(0.6 0.16 145)', // --success (green) + draft: 'oklch(0.75 0.15 85)', // --warning (yellow) + archived: 'oklch(0.556 0 0)', // --muted-foreground (gray) + needs_review: 'oklch(0.58 0.22 27)', // --destructive (red) }; // Custom tooltip component for the pie chart diff --git a/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx b/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx index 73b004b7b..173010f85 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx @@ -1,6 +1,6 @@ -import { Grid } from '@trycompai/design-system'; import { auth } from '@/utils/auth'; import { db } from '@db'; +import { Grid, PageHeader, PageLayout } from '@trycompai/design-system'; import type { Metadata } from 'next'; import { headers } from 'next/headers'; import { Suspense } from 'react'; @@ -12,12 +12,15 @@ export default async function PoliciesOverview() { const overview = await getPoliciesOverview(); return ( - }> - - - - - + + + }> + + + + + + ); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx b/apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx index 65953ea42..f569d7da4 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/all/components/PoliciesTableDS.tsx @@ -1,15 +1,10 @@ 'use client'; -import { CreatePolicySheet } from '@/components/sheets/create-policy-sheet'; import { StatusIndicator } from '@/components/status-indicator'; import { formatDate } from '@/lib/format'; -import { downloadAllPolicies } from '@/lib/pdf-generator'; -import { Add, Download } from '@carbon/icons-react'; -import { Button } from '@comp/ui/button'; import type { Policy } from '@db'; import { Badge, - Stack, Table, TableBody, TableCell, @@ -20,9 +15,7 @@ import { } from '@trycompai/design-system'; import { ExternalLink, Loader2 } from 'lucide-react'; import Link from 'next/link'; -import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { useState } from 'react'; -import { getLogsForPolicy } from '../../[policyId]/data'; +import { useParams, useRouter } from 'next/navigation'; import { usePolicyTailoringStatus, type PolicyTailoringStatus } from './policy-tailoring-context'; interface PoliciesTableDSProps { @@ -34,97 +27,36 @@ const ACTIVE_STATUSES: PolicyTailoringStatus[] = ['queued', 'pending', 'processi export function PoliciesTableDS({ policies }: PoliciesTableDSProps) { const params = useParams<{ orgId: string }>(); const router = useRouter(); - const pathname = usePathname(); - const searchParams = useSearchParams(); const orgId = params.orgId; - const [isDownloadingAll, setIsDownloadingAll] = useState(false); - - const handleDownloadAll = async () => { - setIsDownloadingAll(true); - try { - const logsEntries = await Promise.all( - policies.map(async (policy) => { - const logs = await getLogsForPolicy(policy.id); - return [policy.id, logs] as const; - }), - ); - const policyLogs = Object.fromEntries(logsEntries); - downloadAllPolicies(policies, policyLogs); - } finally { - setIsDownloadingAll(false); - } - }; - - const handleCreatePolicy = () => { - const params = new URLSearchParams(searchParams.toString()); - params.set('create-policy-sheet', 'true'); - router.push(`${pathname}?${params.toString()}`); - }; const handleRowClick = (policyId: string) => { router.push(`/${orgId}/policies/${policyId}`); }; + if (policies.length === 0) { + return ( +
+ No policies found. +
+ ); + } + return ( - <> - -
- {policies.length > 0 && ( - - )} - -
- - {policies.length === 0 ? ( -
- No policies found. -
- ) : ( - - - - Policy Name - Status - Department - Last Updated - - - - {policies.map((policy) => ( - - ))} - -
- )} -
- - - + + + + Policy Name + Status + Department + Last Updated + + + + {policies.map((policy) => ( + + ))} + +
); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/all/components/PolicyPageActions.tsx b/apps/app/src/app/(app)/[orgId]/policies/all/components/PolicyPageActions.tsx new file mode 100644 index 000000000..89a614214 --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/policies/all/components/PolicyPageActions.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { CreatePolicySheet } from '@/components/sheets/create-policy-sheet'; +import { downloadAllPolicies } from '@/lib/pdf-generator'; +import { Add, Download } from '@carbon/icons-react'; +import { Button } from '@trycompai/design-system'; +import type { Policy } from '@db'; +import { Loader2 } from 'lucide-react'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { useState } from 'react'; +import { getLogsForPolicy } from '../../[policyId]/data'; + +interface PolicyPageActionsProps { + policies: Policy[]; +} + +export function PolicyPageActions({ policies }: PolicyPageActionsProps) { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const [isDownloadingAll, setIsDownloadingAll] = useState(false); + + const handleDownloadAll = async () => { + setIsDownloadingAll(true); + try { + const logsEntries = await Promise.all( + policies.map(async (policy) => { + const logs = await getLogsForPolicy(policy.id); + return [policy.id, logs] as const; + }), + ); + const policyLogs = Object.fromEntries(logsEntries); + downloadAllPolicies(policies, policyLogs); + } finally { + setIsDownloadingAll(false); + } + }; + + const handleCreatePolicy = () => { + const params = new URLSearchParams(searchParams.toString()); + params.set('create-policy-sheet', 'true'); + router.push(`${pathname}?${params.toString()}`); + }; + + return ( + <> +
+ {policies.length > 0 && ( + + )} + +
+ + + ); +} diff --git a/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx b/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx index 1ba3d0b41..deff30f98 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/all/page.tsx @@ -1,7 +1,10 @@ import { db } from '@db'; +import { PageHeader, PageLayout } from '@trycompai/design-system'; import type { Metadata } from 'next'; +import { PolicyTabs } from '../components/PolicyTabs'; import { PoliciesTableDS } from './components/PoliciesTableDS'; import { PolicyTailoringProvider } from './components/policy-tailoring-context'; +import { PolicyPageActions } from './components/PolicyPageActions'; interface PolicyTableProps { params: Promise<{ orgId: string }>; @@ -16,9 +19,13 @@ export default async function PoliciesPage({ params }: PolicyTableProps) { }); return ( - - - + + } /> + + + + + ); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/layout.tsx b/apps/app/src/app/(app)/[orgId]/policies/layout.tsx index e1872e661..42088673e 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/layout.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/layout.tsx @@ -1,16 +1,7 @@ -import { PageHeader, PageLayout } from '@trycompai/design-system'; -import { PolicyTabs } from './components/PolicyTabs'; - interface LayoutProps { children: React.ReactNode; } export default function Layout({ children }: LayoutProps) { - return ( - - - - {children} - - ); + return <>{children}; } From b091a68c0013a6adab7a3d9c8eca45131a866a17 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 14:26:33 -0500 Subject: [PATCH 08/21] feat(app): update @trycompai/design-system to version 1.0.8 and enhance components --- apps/app/package.json | 2 +- .../(app)/[orgId]/components/AppShellWrapper.tsx | 13 +++++++++---- .../app/(app)/[orgId]/policies/(overview)/page.tsx | 4 +++- bun.lock | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/app/package.json b/apps/app/package.json index 7e21c9746..7b92d43ea 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -57,7 +57,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", - "@trycompai/design-system": "^1.0.4", + "@trycompai/design-system": "^1.0.8", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", diff --git a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx index b361b0f66..4553f5fa6 100644 --- a/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx +++ b/apps/app/src/app/(app)/[orgId]/components/AppShellWrapper.tsx @@ -48,7 +48,8 @@ import { AvatarFallback, AvatarImage, CommandSearch, - LogoIcon, + HStack, + Logo, Text, ThemeToggle, } from '@trycompai/design-system'; @@ -230,12 +231,16 @@ export function AppShellWrapper({ + - + + / - + } centerContent={} endContent={ diff --git a/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx b/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx index 173010f85..69c436f80 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/(overview)/page.tsx @@ -4,6 +4,7 @@ import { Grid, PageHeader, PageLayout } from '@trycompai/design-system'; import type { Metadata } from 'next'; import { headers } from 'next/headers'; import { Suspense } from 'react'; +import { PolicyTabs } from '../components/PolicyTabs'; import { PolicyAssigneeChart } from './components/policy-assignee-chart'; import { PolicyStatusChart } from './components/policy-status-chart'; import Loading from './loading'; @@ -12,8 +13,9 @@ export default async function PoliciesOverview() { const overview = await getPoliciesOverview(); return ( - + + }> diff --git a/bun.lock b/bun.lock index 8118f4c88..f26248995 100644 --- a/bun.lock +++ b/bun.lock @@ -211,7 +211,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", - "@trycompai/design-system": "^1.0.4", + "@trycompai/design-system": "^1.0.8", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", @@ -2182,7 +2182,7 @@ "@trycompai/db": ["@trycompai/db@workspace:packages/db"], - "@trycompai/design-system": ["@trycompai/design-system@1.0.4", "", { "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-ImzhZI9UR9S54Q+mm8bJIPWmLG/Br+GB0n/dtu6peQJN3ebk03+hPps16uf0Um5ePuqDXnbwGgaqnmKjMMjlTg=="], + "@trycompai/design-system": ["@trycompai/design-system@1.0.8", "", { "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-/67c/Gue2OUiXLxV3WFC5G/papHo7fhn+rWbVFUlIBnFQCCZQJN6+QW8sJC98PqNKIUlPNQUnCm9k5w81v6Nsg=="], "@trycompai/email": ["@trycompai/email@workspace:packages/email"], From 42d849649765b3a9c407a282df1ec709805e0e10 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Mon, 12 Jan 2026 16:07:24 -0500 Subject: [PATCH 09/21] feat(app): update @trycompai/design-system to version 1.0.11 and enhance policy components --- apps/app/agents.md | 32 ++ apps/app/next.config.ts | 7 +- apps/app/package.json | 2 +- .../[policyId]/components/PolicyAlerts.tsx | 170 ++++++++ .../[policyId]/components/PolicyOverview.tsx | 242 ----------- .../[policyId]/components/PolicyPage.tsx | 44 +- .../[policyId]/components/PolicyPageTabs.tsx | 118 +++++ .../components/PolicySettingsCard.tsx | 36 ++ .../components/UpdatePolicyOverview.tsx | 320 +++++++------- .../editor/components/PolicyDetails.tsx | 197 +++++---- .../components/ai/policy-ai-assistant.tsx | 406 ++++++++++-------- .../[orgId]/policies/[policyId]/page.tsx | 27 +- .../all/components/PolicyPageActions.tsx | 29 +- .../(app)/[orgId]/risk/(overview)/page.tsx | 5 +- .../forms/policies/create-new-policy.tsx | 130 +++--- .../src/components/sheets/assistant-sheet.tsx | 2 +- .../components/sheets/create-policy-sheet.tsx | 47 +- apps/app/src/styles/editor.css | 191 ++++++++ bun.lock | 4 +- packages/ui/src/components/editor/index.tsx | 2 +- packages/ui/src/editor.css | 38 +- 21 files changed, 1174 insertions(+), 875 deletions(-) create mode 100644 apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyAlerts.tsx delete mode 100644 apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyOverview.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPageTabs.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicySettingsCard.tsx create mode 100644 apps/app/src/styles/editor.css diff --git a/apps/app/agents.md b/apps/app/agents.md index 558d718f7..ab953de9c 100644 --- a/apps/app/agents.md +++ b/apps/app/agents.md @@ -213,3 +213,35 @@ import { // ... etc } from '@trycompai/design-system'; ``` + +## Icons + +Import icons from `@trycompai/design-system/icons` (re-exports `@carbon/icons-react`): + +```tsx +// ✅ ALWAYS - Use design system icons +import { Add, Download, Settings, ChevronDown } from '@trycompai/design-system/icons'; + +// Carbon icons use size prop, not className + + + +// ❌ NEVER - Don't use lucide-react +import { Plus, Download } from 'lucide-react'; + +``` + +Common icon mappings from lucide-react to Carbon: + +| lucide-react | @carbon/icons-react | +|--------------|---------------------| +| Plus | Add | +| X | Close | +| Check | Checkmark | +| ChevronDown | ChevronDown | +| ChevronRight | ChevronRight | +| Settings | Settings | +| Trash | TrashCan | +| Edit | Edit | +| Search | Search | +| Loader2 | (use Button loading prop) | diff --git a/apps/app/next.config.ts b/apps/app/next.config.ts index bc9f4839e..4369872e2 100644 --- a/apps/app/next.config.ts +++ b/apps/app/next.config.ts @@ -41,7 +41,12 @@ const config: NextConfig = { ? `${process.env.STATIC_ASSETS_URL}/app` : '', reactStrictMode: false, - transpilePackages: ['@trycompai/db', '@prisma/client', '@trycompai/design-system'], + transpilePackages: [ + '@trycompai/db', + '@prisma/client', + '@trycompai/design-system', + '@carbon/icons-react', + ], images: { remotePatterns: [ { diff --git a/apps/app/package.json b/apps/app/package.json index 7b92d43ea..2d1b24030 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -57,7 +57,7 @@ "@trigger.dev/react-hooks": "4.0.6", "@trigger.dev/sdk": "4.0.6", "@trycompai/db": "^1.3.20", - "@trycompai/design-system": "^1.0.8", + "@trycompai/design-system": "^1.0.11", "@trycompai/email": "workspace:*", "@types/canvas-confetti": "^1.9.0", "@types/react-syntax-highlighter": "^15.5.13", diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyAlerts.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyAlerts.tsx new file mode 100644 index 000000000..8cd1cbfbc --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyAlerts.tsx @@ -0,0 +1,170 @@ +'use client'; + +import { acceptRequestedPolicyChangesAction } from '@/actions/policies/accept-requested-policy-changes'; +import { denyRequestedPolicyChangesAction } from '@/actions/policies/deny-requested-policy-changes'; +import { authClient } from '@/utils/auth-client'; +import { Alert, AlertDescription, AlertTitle } from '@comp/ui/alert'; +import { Button } from '@comp/ui/button'; +import type { Member, Policy, User } from '@db'; +import { Archive, CheckmarkFilled, CloseFilled, Renew } from '@trycompai/design-system/icons'; +import { format } from 'date-fns'; +import { useAction } from 'next-safe-action/hooks'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useState } from 'react'; +import { toast } from 'sonner'; +import { PolicyActionDialog } from './PolicyActionDialog'; + +interface PolicyAlertsProps { + policy: (Policy & { approver: (Member & { user: User }) | null }) | null; + isPendingApproval: boolean; +} + +export function PolicyAlerts({ policy, isPendingApproval }: PolicyAlertsProps) { + const { data: activeMember } = authClient.useActiveMember(); + const router = useRouter(); + const searchParams = useSearchParams(); + const canCurrentUserApprove = policy?.approverId === activeMember?.id; + + const [approveDialogOpen, setApproveDialogOpen] = useState(false); + const [denyDialogOpen, setDenyDialogOpen] = useState(false); + + const denyPolicyChanges = useAction(denyRequestedPolicyChangesAction, { + onSuccess: () => { + toast.info('Policy changes denied!'); + window.location.reload(); + }, + onError: () => { + toast.error('Failed to deny policy changes.'); + }, + }); + + const acceptPolicyChanges = useAction(acceptRequestedPolicyChangesAction, { + onSuccess: () => { + toast.success('Policy changes accepted and published!'); + window.location.reload(); + }, + onError: () => { + toast.error('Failed to accept policy changes.'); + }, + }); + + const handleApprove = (comment?: string) => { + if (policy?.id && policy.approverId) { + acceptPolicyChanges.execute({ + id: policy.id, + approverId: policy.approverId, + comment, + entityId: policy.id, + }); + } + }; + + const handleDeny = (comment?: string) => { + if (policy?.id && policy.approverId) { + denyPolicyChanges.execute({ + id: policy.id, + approverId: policy.approverId, + comment, + entityId: policy.id, + }); + } + }; + + const handleOpenArchiveSheet = () => { + const params = new URLSearchParams(searchParams.toString()); + params.set('archive-policy-sheet', 'true'); + router.push(`?${params.toString()}`); + }; + + if (!policy) { + return null; + } + + const showPendingAlert = isPendingApproval; + const showArchivedAlert = policy.isArchived; + + if (!showPendingAlert && !showArchivedAlert) { + return null; + } + + return ( + <> + {showPendingAlert && ( + + + + {canCurrentUserApprove ? 'Action Required by You' : 'Pending Approval'} + + +
+ This policy is awaiting approval from{' '} + + {policy.approverId === activeMember?.id + ? 'you' + : `${policy?.approver?.user?.name} (${policy?.approver?.user?.email})`} + + . +
+ {canCurrentUserApprove && + ' Please review the details and either approve or reject the changes.'} + {!canCurrentUserApprove && ' All fields are disabled until the policy is actioned.'} + {isPendingApproval && policy.approverId && canCurrentUserApprove && ( +
+ + +
+ )} +
+
+ )} + + {showArchivedAlert && ( + +
+ +
+
This policy is archived
+ + Archived on {format(new Date(policy?.updatedAt ?? new Date()), 'PPP')} + +
+
+
+ +
+
+ )} + + {/* Approval Dialog */} + setApproveDialogOpen(false)} + onConfirm={handleApprove} + title="Approve Policy Changes" + description="Are you sure you want to approve these policy changes? You can optionally add a comment that will be visible in the policy history." + confirmText="Approve" + confirmIcon={} + /> + + {/* Denial Dialog */} + setDenyDialogOpen(false)} + onConfirm={handleDeny} + title="Deny Policy Changes" + description="Are you sure you want to deny these policy changes? You can optionally add a comment explaining your decision that will be visible in the policy history." + confirmText="Deny" + confirmIcon={} + confirmVariant="destructive" + /> + + ); +} diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyOverview.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyOverview.tsx deleted file mode 100644 index f8fb688b0..000000000 --- a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyOverview.tsx +++ /dev/null @@ -1,242 +0,0 @@ -'use client'; - -import { acceptRequestedPolicyChangesAction } from '@/actions/policies/accept-requested-policy-changes'; -import { denyRequestedPolicyChangesAction } from '@/actions/policies/deny-requested-policy-changes'; -import { authClient } from '@/utils/auth-client'; -import { Alert, AlertDescription, AlertTitle } from '@comp/ui/alert'; -import { Button } from '@comp/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@comp/ui/card'; -import { Icons } from '@comp/ui/icons'; -import type { Member, Policy, User } from '@db'; -import { Control } from '@db'; -import { format } from 'date-fns'; -import { ArchiveIcon, ArchiveRestoreIcon, ShieldCheck, ShieldX } from 'lucide-react'; -import { useAction } from 'next-safe-action/hooks'; -import { useQueryState } from 'nuqs'; -import { useState } from 'react'; -import { toast } from 'sonner'; -import { regeneratePolicyAction } from '../actions/regenerate-policy'; -import { PolicyActionDialog } from './PolicyActionDialog'; -import { PolicyArchiveSheet } from './PolicyArchiveSheet'; -import { PolicyControlMappings } from './PolicyControlMappings'; -import { PolicyDeleteDialog } from './PolicyDeleteDialog'; -import { PolicyOverviewSheet } from './PolicyOverviewSheet'; -import { UpdatePolicyOverview } from './UpdatePolicyOverview'; - -export function PolicyOverview({ - policy, - assignees, - mappedControls, - allControls, - isPendingApproval, -}: { - policy: (Policy & { approver: (Member & { user: User }) | null }) | null; - assignees: (Member & { user: User })[]; - mappedControls: Control[]; - allControls: Control[]; - isPendingApproval: boolean; -}) { - const { data: activeMember } = authClient.useActiveMember(); - const [, setArchiveOpen] = useQueryState('archive-policy-sheet'); - const canCurrentUserApprove = policy?.approverId === activeMember?.id; - - const denyPolicyChanges = useAction(denyRequestedPolicyChangesAction, { - onSuccess: () => { - toast.info('Policy changes denied!'); - // Force a complete page reload instead of just a refresh - window.location.reload(); - }, - onError: () => { - toast.error('Failed to deny policy changes.'); - }, - }); - - const acceptPolicyChanges = useAction(acceptRequestedPolicyChangesAction, { - onSuccess: () => { - toast.success('Policy changes accepted and published!'); - // Force a complete page reload instead of just a refresh - window.location.reload(); - }, - onError: () => { - toast.error('Failed to accept policy changes.'); - }, - }); - - // Dialog state for approval/denial actions - const [approveDialogOpen, setApproveDialogOpen] = useState(false); - const [denyDialogOpen, setDenyDialogOpen] = useState(false); - const [deleteOpenParam, setDeleteOpenParam] = useQueryState('delete-policy'); - const [regenerateOpen, setRegenerateOpen] = useState(false); - - // Handle approve with optional comment - const handleApprove = (comment?: string) => { - if (policy?.id && policy.approverId) { - acceptPolicyChanges.execute({ - id: policy.id, - approverId: policy.approverId, - comment, - entityId: policy.id, - }); - } - }; - - // Handle deny with optional comment - const handleDeny = (comment?: string) => { - if (policy?.id && policy.approverId) { - denyPolicyChanges.execute({ - id: policy.id, - approverId: policy.approverId, - comment, - entityId: policy.id, - }); - } - }; - - if (!policy) { - return null; - } - - return ( -
- {isPendingApproval && ( - - - - {canCurrentUserApprove ? 'Action Required by You' : 'Pending Approval'} - - -
- This policy is awaiting approval from{' '} - - {policy.approverId === activeMember?.id - ? 'you' - : `${policy?.approver?.user?.name} (${policy?.approver?.user?.email})`} - - . -
- {canCurrentUserApprove && - ' Please review the details and either approve or reject the changes.'} - {!canCurrentUserApprove && ' All fields are disabled until the policy is actioned.'} - {isPendingApproval && policy.approverId && canCurrentUserApprove && ( -
- - -
- )} -
-
- )} - {policy?.isArchived && ( - -
- -
-
This policy is archived
- - Archived on {format(new Date(policy?.updatedAt ?? new Date()), 'PPP')} - -
-
-
- -
-
- )} - - - - -
-
- - {policy?.name} -
- {/* Redundant gear removed; actions moved to breadcrumb header */} -
-
- - {policy?.description} - - - {policy && ( - - )} - - - - - - {policy && ( - <> - - - - {/* Approval Dialog */} - setApproveDialogOpen(false)} - onConfirm={handleApprove} - title="Approve Policy Changes" - description="Are you sure you want to approve these policy changes? You can optionally add a comment that will be visible in the policy history." - confirmText="Approve" - confirmIcon={} - /> - - {/* Denial Dialog */} - setDenyDialogOpen(false)} - onConfirm={handleDeny} - title="Deny Policy Changes" - description="Are you sure you want to deny these policy changes? You can optionally add a comment explaining your decision that will be visible in the policy history." - confirmText="Deny" - confirmIcon={} - confirmVariant="destructive" - /> - - {/* Delete Dialog */} - setDeleteOpenParam(null)} - policy={policy} - /> - {/* Regenerate Dialog */} - setRegenerateOpen(false)} - onConfirm={async () => { - if (!policy?.id) return; - await regeneratePolicyAction({ policyId: policy.id }); - toast.info('Regeneration started'); - }} - title="Regenerate Policy" - description="This will regenerate the policy content. Continue?" - confirmText="Regenerate" - confirmIcon={} - /> - - )} -
- ); -} diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPage.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPage.tsx index c0e1dfded..629ba9b90 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPage.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPage.tsx @@ -1,10 +1,6 @@ -import { Control, Member, Policy, User } from '@db'; -import type { JSONContent } from '@tiptap/react'; -import { Comments } from '../../../../../../components/comments/Comments'; -import { AuditLogWithRelations } from '../data'; -import { PolicyContentManager } from '../editor/components/PolicyDetails'; -import { PolicyOverview } from './PolicyOverview'; -import { RecentAuditLogs } from './RecentAuditLogs'; +import type { Control, Member, Policy, User } from '@db'; +import type { AuditLogWithRelations } from '../data'; +import { PolicyPageTabs } from './PolicyPageTabs'; export default function PolicyPage({ policy, @@ -23,33 +19,21 @@ export default function PolicyPage({ allControls: Control[]; isPendingApproval: boolean; policyId: string; - /** Organization ID - required for correct org context in comments */ organizationId: string; logs: AuditLogWithRelations[]; - /** Whether the AI assistant feature is enabled */ showAiAssistant: boolean; }) { return ( - <> - - - - - - - + ); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPageTabs.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPageTabs.tsx new file mode 100644 index 000000000..8d0c12f6b --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPageTabs.tsx @@ -0,0 +1,118 @@ +'use client'; + +import type { Control, Member, Policy, User } from '@db'; +import type { JSONContent } from '@tiptap/react'; +import { Stack, Tabs, TabsContent, TabsList, TabsTrigger } from '@trycompai/design-system'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { Comments } from '../../../../../../components/comments/Comments'; +import type { AuditLogWithRelations } from '../data'; +import { PolicyContentManager } from '../editor/components/PolicyDetails'; +import { PolicyAlerts } from './PolicyAlerts'; +import { PolicyArchiveSheet } from './PolicyArchiveSheet'; +import { PolicyControlMappings } from './PolicyControlMappings'; +import { PolicyDeleteDialog } from './PolicyDeleteDialog'; +import { PolicyOverviewSheet } from './PolicyOverviewSheet'; +import { PolicySettingsCard } from './PolicySettingsCard'; +import { RecentAuditLogs } from './RecentAuditLogs'; + +interface PolicyPageTabsProps { + policy: (Policy & { approver: (Member & { user: User }) | null }) | null; + assignees: (Member & { user: User })[]; + mappedControls: Control[]; + allControls: Control[]; + isPendingApproval: boolean; + policyId: string; + organizationId: string; + logs: AuditLogWithRelations[]; + showAiAssistant: boolean; +} + +export function PolicyPageTabs({ + policy, + assignees, + mappedControls, + allControls, + isPendingApproval, + policyId, + organizationId, + logs, + showAiAssistant, +}: PolicyPageTabsProps) { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const isDeleteDialogOpen = searchParams.get('delete-policy') === 'true'; + + const handleCloseDeleteDialog = () => { + const params = new URLSearchParams(searchParams.toString()); + params.delete('delete-policy'); + const query = params.toString(); + router.push(query ? `${pathname}?${query}` : pathname); + }; + + return ( + + {/* Alerts always visible above tabs */} + + + + + + Overview + Content + Activity + Comments + + + + + + + + + + + + + + + + + + + + + + + + {/* Sheets and dialogs that can be triggered from anywhere */} + {policy && ( + <> + + + + + )} + + ); +} diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicySettingsCard.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicySettingsCard.tsx new file mode 100644 index 000000000..6ca817b6e --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicySettingsCard.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { Card, CardContent, CardHeader, CardTitle } from '@comp/ui/card'; +import type { Member, Policy, User } from '@db'; +import { UpdatePolicyOverview } from './UpdatePolicyOverview'; + +interface PolicySettingsCardProps { + policy: (Policy & { approver: (Member & { user: User }) | null }) | null; + assignees: (Member & { user: User })[]; + isPendingApproval: boolean; +} + +export function PolicySettingsCard({ + policy, + assignees, + isPendingApproval, +}: PolicySettingsCardProps) { + if (!policy) { + return null; + } + + return ( + + + Policy Settings + + + + + + ); +} diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/UpdatePolicyOverview.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/UpdatePolicyOverview.tsx index a83c9b69d..75205812a 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/UpdatePolicyOverview.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/UpdatePolicyOverview.tsx @@ -4,12 +4,24 @@ import { submitPolicyForApprovalAction } from '@/actions/policies/submit-policy- import { updatePolicyFormAction } from '@/actions/policies/update-policy-form-action'; import { SelectAssignee } from '@/components/SelectAssignee'; import { StatusIndicator } from '@/components/status-indicator'; -import { Button } from '@comp/ui/button'; -import { cn } from '@comp/ui/cn'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@comp/ui/select'; import { Departments, Frequency, Member, type Policy, PolicyStatus, User } from '@db'; +import { + Button, + Grid, + HStack, + InputGroup, + InputGroupAddon, + InputGroupInput, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Stack, +} from '@trycompai/design-system'; +import { Calendar } from '@trycompai/design-system/icons'; import { format } from 'date-fns'; -import { CalendarIcon, Loader2 } from 'lucide-react'; import { useAction } from 'next-safe-action/hooks'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -29,21 +41,13 @@ export function UpdatePolicyOverview({ assignees, isPendingApproval, }: UpdatePolicyOverviewProps) { - // Dialog state only - no form state const [isApprovalDialogOpen, setIsApprovalDialogOpen] = useState(false); const [selectedApproverId, setSelectedApproverId] = useState(null); const router = useRouter(); - // Track selected status const [selectedStatus, setSelectedStatus] = useState(policy.status); - - // Track selected assignee const [selectedAssigneeId, setSelectedAssigneeId] = useState(policy.assigneeId); - - // Loading state const [isSubmitting, setIsSubmitting] = useState(false); - - // Track form interactions to determine button text const [formInteracted, setFormInteracted] = useState(false); const fieldsDisabled = isPendingApproval; @@ -52,7 +56,7 @@ export function UpdatePolicyOverview({ onSuccess: () => { toast.success('Policy updated successfully'); setIsSubmitting(false); - setFormInteracted(false); // Reset form interaction state after successful update + setFormInteracted(false); router.refresh(); }, onError: () => { @@ -66,7 +70,7 @@ export function UpdatePolicyOverview({ toast.success('Policy submitted for approval successfully!'); setIsSubmitting(false); setIsApprovalDialogOpen(false); - setFormInteracted(false); // Reset form interaction state after successful submission + setFormInteracted(false); setSelectedStatus('needs_review'); router.refresh(); }, @@ -76,7 +80,6 @@ export function UpdatePolicyOverview({ }, }); - // Function to handle form field changes const handleFormChange = () => { setFormInteracted(true); }; @@ -85,17 +88,13 @@ export function UpdatePolicyOverview({ e.preventDefault(); setIsSubmitting(true); - // Get form data directly from the form element const formData = new FormData(e.currentTarget); const status = formData.get('status') as PolicyStatus; - const assigneeId = selectedAssigneeId; // Use state instead of form data + const assigneeId = selectedAssigneeId; const department = formData.get('department') as Departments; const reviewFrequency = formData.get('review_frequency') as Frequency; - - // Get review date from the form or use the existing one const reviewDate = policy.reviewDate ? new Date(policy.reviewDate) : new Date(); - // Check if the policy is published and if there are changes const isPublishedWithChanges = policy.status === 'published' && (status !== policy.status || @@ -105,8 +104,10 @@ export function UpdatePolicyOverview({ (policy.reviewDate ? new Date(policy.reviewDate).toDateString() : '') !== reviewDate.toDateString()); - // If policy is draft and being published OR policy is published and has changes - if ((['draft', 'needs_review'].includes(policy.status) && status === 'published') || isPublishedWithChanges) { + if ( + (['draft', 'needs_review'].includes(policy.status) && status === 'published') || + isPublishedWithChanges + ) { setIsApprovalDialogOpen(true); setIsSubmitting(false); } else { @@ -129,14 +130,11 @@ export function UpdatePolicyOverview({ return; } - // Get form data directly from the DOM const form = document.getElementById('policy-form') as HTMLFormElement; const formData = new FormData(form); - const assigneeId = selectedAssigneeId; // Use state instead of form data + const assigneeId = selectedAssigneeId; const department = formData.get('department') as Departments; const reviewFrequency = formData.get('review_frequency') as Frequency; - - // Get review date from the form or use the existing one const reviewDate = policy.reviewDate ? new Date(policy.reviewDate) : new Date(); setIsSubmitting(true); @@ -153,10 +151,8 @@ export function UpdatePolicyOverview({ setSelectedApproverId(null); }; - // Check if form has been modified to determine button state const hasFormChanges = formInteracted; - // Determine button text based on status and form interaction let buttonText = 'Save'; if ( (['draft', 'needs_review'].includes(policy.status) && selectedStatus === 'published') || @@ -165,162 +161,142 @@ export function UpdatePolicyOverview({ buttonText = 'Submit for Approval'; } + const isLoading = isSubmitting || updatePolicyForm.isExecuting || submitForApproval.isExecuting; + return ( <> -
-
- {/* Status Field */} -
- - {/* Hidden input for form submission */} - - -
+ + + + {/* Status Field */} + + + + + - {/* Review Frequency Field */} -
- - -
+ {/* Review Frequency Field */} + + + + - {/* Department Field */} -
- - + + + + + {Object.values(Departments).map((dept) => ( - {formattedDepartment} + {dept.toUpperCase()} - ); - })} - - -
+ ))} + + +
+ + {/* Assignee Field */} + + + { + setSelectedAssigneeId(id); + handleFormChange(); + }} + assigneeId={selectedAssigneeId || ''} + disabled={fieldsDisabled} + withTitle={false} + /> + - {/* Assignee Field */} -
- - { - setSelectedAssigneeId(id); - handleFormChange(); - }} - assigneeId={selectedAssigneeId || ''} - disabled={fieldsDisabled} - withTitle={false} - /> -
+ {/* Review Date Field */} + + + + + + + + + + + - {/* Review Date Field */} -
- -
+ {!isPendingApproval && ( + -
- {/* Hidden input to store the date value */} - -
-
- -
- {!isPendingApproval && ( - + )} -
+
>(() => { const formattedContent = Array.isArray(policyContent) @@ -105,11 +114,6 @@ export function PolicyContentManager({ const [dismissedProposalKey, setDismissedProposalKey] = useState(null); const [isApplying, setIsApplying] = useState(false); const [chatErrorMessage, setChatErrorMessage] = useState(null); - const diffViewerRef = useRef(null); - - const scrollToDiffViewer = useCallback(() => { - diffViewerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }, []); const { messages, @@ -187,99 +191,96 @@ export function PolicyContentManager({ } return ( -
- - -
-
- - switchFormat.execute({ policyId, format: format as 'EDITOR' | 'PDF' }) - } - className="w-full" - > -
- - - Editor View - - - PDF View - - - {!isPendingApproval && aiAssistantEnabled && ( - - )} -
- - - - - - -
-
- - {aiAssistantEnabled && showAiAssistant && ( -
- setShowAiAssistant(false)} - onScrollToDiff={scrollToDiffViewer} - hasActiveProposal={!!activeProposal && !hasPendingProposal} - /> -
- )} -
-
-
+ + + switchFormat.execute({ policyId, format: format as 'EDITOR' | 'PDF' }) + } + > + + + + Editor View + + + PDF View + + + {!isPendingApproval && aiAssistantEnabled && ( + + )} + + + + + + + + + + + + + {aiAssistantEnabled && showAiAssistant && ( + setShowAiAssistant(false)} + hasActiveProposal={!!activeProposal && !hasPendingProposal} + /> + )} + + {proposedPolicyMarkdown && diffPatch && activeProposal && !hasPendingProposal && ( -
-
+ + - -
+ -
+
)} -
+ ); } @@ -370,8 +371,14 @@ function PolicyEditorWrapper({ } return ( -
- -
+ + + + + ); } diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/editor/components/ai/policy-ai-assistant.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/editor/components/ai/policy-ai-assistant.tsx index ec617d7c7..f915a833f 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/editor/components/ai/policy-ai-assistant.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/editor/components/ai/policy-ai-assistant.tsx @@ -1,29 +1,24 @@ 'use client'; import { - Conversation, - ConversationContent, - ConversationScrollButton, -} from '@comp/ui/ai-elements/conversation'; + AIChatBody, + Badge, + Button, + Card, + HStack, + Stack, + Text, + Textarea, +} from '@trycompai/design-system'; import { - PromptInput, - PromptInputFooter, - PromptInputSubmit, - PromptInputTextarea, -} from '@comp/ui/ai-elements/prompt-input'; -import { Tool, ToolHeader } from '@comp/ui/ai-elements/tool'; -import { Badge } from '@comp/ui/badge'; -import { Button } from '@comp/ui/button'; -import { cn } from '@comp/ui/cn'; + ArrowDown, + CheckmarkFilled, + CircleFilled, + Close, + Error, + Time, +} from '@trycompai/design-system/icons'; import type { ChatStatus } from 'ai'; -import { - ArrowDownIcon, - CheckCircleIcon, - CircleIcon, - ClockIcon, - X, - XCircleIcon, -} from 'lucide-react'; import { useState } from 'react'; import type { PolicyChatUIMessage } from '../../types'; @@ -33,7 +28,6 @@ interface PolicyAiAssistantProps { errorMessage?: string | null; sendMessage: (payload: { text: string }) => void; close?: () => void; - onScrollToDiff?: () => void; hasActiveProposal?: boolean; } @@ -43,7 +37,6 @@ export function PolicyAiAssistant({ errorMessage, sendMessage, close, - onScrollToDiff, hasActiveProposal, }: PolicyAiAssistantProps) { const [input, setInput] = useState(''); @@ -68,174 +61,219 @@ export function PolicyAiAssistant({ setInput(''); }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSubmit(); + } + }; + return ( -
-
- AI Assistant - {close && ( - + ) + } + > + + + + {messages.length === 0 ? ( + + + I can help you edit, adapt, or check this policy for compliance. Try asking me + things like: + + + + "Adapt this for a fully remote, distributed team." + + + "Can I shorten the data retention timeframe and still meet SOC 2 standards?" + + + "Modify the access control section to include contractors." + + + + ) : ( + messages.map((message) => ( + + )) + )} + {isLoading && !hasActiveTool && ( + + Thinking... + + )} + + + + {errorMessage && ( + + + {errorMessage} + + )} -
- - - - {messages.length === 0 ? ( -
-

- I can help you edit, adapt, or check this policy for compliance. Try asking me - things like: -

-
    -
  • "Adapt this for a fully remote, distributed team."
  • -
  • - "Can I shorten the data retention timeframe and still meet SOC 2 standards?" -
  • -
  • "Modify the access control section to include contractors."
  • -
-
- ) : ( - messages.map((message) => ( -
- {message.parts.map((part, index) => { - if (part.type === 'text') { - return ( -
- {part.text} -
- ); - } - - if (part.type === 'tool-proposePolicy') { - const toolInput = part.input; - - const isInProgress = - part.state === 'input-streaming' || part.state === 'input-available'; - const isCompleted = part.state === 'output-available'; - - const title = - (isCompleted - ? toolInput?.title || toolInput?.summary - : toolInput?.title || 'Configuring policy updates') || - 'Policy updates ready for your review'; - - const bodyText = (() => { - if (isInProgress) { - return ( - toolInput?.detail || - 'I am preparing an updated version of this policy. Please wait a moment before accepting any changes.' - ); - } - if (isCompleted) { - return ( - toolInput?.detail || - 'The updated policy is ready. Review the diff in the editor before applying changes.' - ); - } - return ( - toolInput?.detail || - 'Review the proposed changes in the editor preview below before applying them.' - ); - })(); - - const truncatedBodyText = - bodyText.length > 180 ? `${bodyText.slice(0, 177)}…` : bodyText; - - type ToolState = typeof part.state; - const statusPill = (() => { - const labels: Record = { - 'input-streaming': 'Drafting', - 'input-available': 'Running', - 'output-available': 'Completed', - 'output-error': 'Error', - }; - - const icons: Record = { - 'input-streaming': , - 'input-available': , - 'output-available': , - 'output-error': , - }; - - return ( - - {icons[part.state]} - {labels[part.state]} - - ); - })(); - - return ( - - -

- {truncatedBodyText} - {hasActiveProposal && onScrollToDiff && ( - - )} -

-
- ); - } - - return null; - })} -
- )) - )} - {isLoading && !hasActiveTool && ( -
Thinking...
- )} -
- -
- - {errorMessage && ( -
-

{errorMessage}

-
- )} - -
- - +