diff --git a/CLAUDE.md b/CLAUDE.md
index 523ff9ae7..fc57dda3d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -109,6 +109,7 @@ Every customer-facing API endpoint MUST have:
- **DS components that do NOT accept `className`**: `Text`, `Stack`, `HStack`, `Badge`, `Button` — wrap in `
` for custom styling
- **Layout**: Use `PageLayout`, `PageHeader`, `Stack`, `HStack`, `Section`, `SettingGroup`
- **Patterns**: Sheet (`Sheet > SheetContent > SheetHeader + SheetBody`), Drawer, Collapsible
+- **After editing any frontend component**: Run the `audit-design-system` skill to catch `@comp/ui` or `lucide-react` imports that should be migrated
## Data Fetching
diff --git a/apps/api/src/auth/auth.server.ts b/apps/api/src/auth/auth.server.ts
index 1085f81bd..6f53b8ec3 100644
--- a/apps/api/src/auth/auth.server.ts
+++ b/apps/api/src/auth/auth.server.ts
@@ -272,8 +272,13 @@ export const auth = betterAuth({
magicLink({
expiresIn: MAGIC_LINK_EXPIRES_IN_SECONDS,
sendMagicLink: async ({ email, url }) => {
+ // The `url` from better-auth points to the API's verify endpoint
+ // and includes the callbackURL from the client's sign-in request.
+ // Flow: user clicks link → API verifies token & sets session cookie
+ // → API redirects (302) to callbackURL (the app).
if (process.env.NODE_ENV === 'development') {
console.log('[Auth] Sending magic link to:', email);
+ console.log('[Auth] Magic link URL:', url);
}
await triggerEmail({
to: email,
diff --git a/apps/api/src/email/trigger-email.ts b/apps/api/src/email/trigger-email.ts
index f95b61f01..735c89182 100644
--- a/apps/api/src/email/trigger-email.ts
+++ b/apps/api/src/email/trigger-email.ts
@@ -14,34 +14,43 @@ export async function triggerEmail(params: {
scheduledAt?: string;
attachments?: EmailAttachment[];
}): Promise<{ id: string }> {
- const html = await render(params.react);
+ try {
+ const html = await render(params.react);
- const fromMarketing = process.env.RESEND_FROM_MARKETING;
- const fromSystem = process.env.RESEND_FROM_SYSTEM;
- const fromDefault = process.env.RESEND_FROM_DEFAULT;
+ const fromMarketing = process.env.RESEND_FROM_MARKETING;
+ const fromSystem = process.env.RESEND_FROM_SYSTEM;
+ const fromDefault = process.env.RESEND_FROM_DEFAULT;
- const fromAddress = params.marketing
- ? fromMarketing
- : params.system
- ? fromSystem
- : fromDefault;
+ const fromAddress = params.marketing
+ ? fromMarketing
+ : params.system
+ ? fromSystem
+ : fromDefault;
- const handle = await tasks.trigger
('send-email', {
- to: params.to,
- subject: params.subject,
- html,
- from: fromAddress ?? undefined,
- cc: params.cc,
- scheduledAt: params.scheduledAt,
- attachments: params.attachments?.map((att) => ({
- filename: att.filename,
- content:
- typeof att.content === 'string'
- ? att.content
- : att.content.toString('base64'),
- contentType: att.contentType,
- })),
- });
+ const handle = await tasks.trigger('send-email', {
+ to: params.to,
+ subject: params.subject,
+ html,
+ from: fromAddress ?? undefined,
+ cc: params.cc,
+ scheduledAt: params.scheduledAt,
+ attachments: params.attachments?.map((att) => ({
+ filename: att.filename,
+ content:
+ typeof att.content === 'string'
+ ? att.content
+ : att.content.toString('base64'),
+ contentType: att.contentType,
+ })),
+ });
- return { id: handle.id };
+ return { id: handle.id };
+ } catch (error) {
+ console.error('[triggerEmail] Failed to trigger email task', {
+ to: params.to,
+ subject: params.subject,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ throw error;
+ }
}
diff --git a/apps/api/src/trigger/email/send-email.ts b/apps/api/src/trigger/email/send-email.ts
index b37357208..36a99bdce 100644
--- a/apps/api/src/trigger/email/send-email.ts
+++ b/apps/api/src/trigger/email/send-email.ts
@@ -32,10 +32,13 @@ export const sendEmailTask = schemaTask({
}),
run: async (params) => {
if (!resend) {
+ logger.error('Resend not initialized - missing RESEND_API_KEY', {
+ to: params.to,
+ subject: params.subject,
+ });
throw new Error('Resend not initialized - missing API key');
}
- const fromMarketing = process.env.RESEND_FROM_MARKETING;
const fromSystem = process.env.RESEND_FROM_SYSTEM;
const fromDefault = process.env.RESEND_FROM_DEFAULT;
const toTest = process.env.RESEND_TO_TEST;
@@ -47,27 +50,40 @@ export const sendEmailTask = schemaTask({
throw new Error('Missing FROM address in environment variables');
}
- const { data, error } = await resend.emails.send({
- from: fromAddress,
- to: toAddress,
- cc: params.cc,
- subject: params.subject,
- html: params.html,
- scheduledAt: params.scheduledAt,
- attachments: params.attachments?.map((att) => ({
- filename: att.filename,
- content: att.content,
- contentType: att.contentType,
- })),
- });
+ try {
+ const { data, error } = await resend.emails.send({
+ from: fromAddress,
+ to: toAddress,
+ cc: params.cc,
+ subject: params.subject,
+ html: params.html,
+ scheduledAt: params.scheduledAt,
+ attachments: params.attachments?.map((att) => ({
+ filename: att.filename,
+ content: att.content,
+ contentType: att.contentType,
+ })),
+ });
- if (error) {
- logger.error('Resend API error', { error });
- throw new Error(`Failed to send email: ${error.message}`);
- }
+ if (error) {
+ logger.error('Resend API error', {
+ error,
+ to: params.to,
+ subject: params.subject,
+ });
+ throw new Error(`Failed to send email: ${error.message}`);
+ }
- logger.info('Email sent', { to: params.to, id: data?.id });
+ logger.info('Email sent', { to: params.to, id: data?.id });
- return { id: data?.id };
+ return { id: data?.id };
+ } catch (error) {
+ logger.error('Email sending failed', {
+ to: params.to,
+ subject: params.subject,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ throw error;
+ }
},
});
diff --git a/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx
index 73a91a2ca..a3198136a 100644
--- a/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx
+++ b/apps/app/src/app/(app)/[orgId]/components/AppSidebar.tsx
@@ -103,12 +103,6 @@ export function AppSidebar({
name: 'Cloud Tests',
hidden: !canAccessRoute(permissions, 'cloud-tests'),
},
- {
- id: 'penetration-tests',
- path: `/${organization.id}/security/penetration-tests`,
- name: 'Penetration Tests',
- hidden: !canAccessRoute(permissions, 'penetration-tests'),
- },
];
const isPathActive = (itemPath: string) => {
diff --git a/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/AutomationRunsCard.tsx b/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/AutomationRunsCard.tsx
index 9fc95fdd1..61d3a9c08 100644
--- a/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/AutomationRunsCard.tsx
+++ b/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/AutomationRunsCard.tsx
@@ -4,8 +4,9 @@ import { Badge } from '@comp/ui/badge';
import { EvidenceAutomationRun, EvidenceAutomationRunStatus } from '@db';
import { Stack, Text, Button } from '@trycompai/design-system';
import { formatDistanceToNow } from 'date-fns';
-import { ChevronDown } from 'lucide-react';
-import { useMemo, useState } from 'react';
+import { toast } from 'sonner';
+import { CheckmarkFilled, ChevronDown, CopyToClipboard } from '@trycompai/design-system/icons';
+import { useCallback, useMemo, useState } from 'react';
type AutomationRunWithName = EvidenceAutomationRun & {
evidenceAutomation: {
@@ -32,6 +33,39 @@ const getStatusStyles = (status: EvidenceAutomationRunStatus) => {
}
};
+function CopyableCodeBlock({ label, content }: { label: string; content: unknown }) {
+ const [copied, setCopied] = useState(false);
+ const text = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
+
+ const handleCopy = useCallback(() => {
+ navigator.clipboard.writeText(text);
+ setCopied(true);
+ toast.success('Copied to clipboard');
+ setTimeout(() => setCopied(false), 2000);
+ }, [text]);
+
+ return (
+
+
{label}
+
+
+
+ {copied ? : }
+
+
+
+ {text}
+
+
+
+ );
+}
+
export function AutomationRunsCard({ runs }: AutomationRunsCardProps) {
const [expandedId, setExpandedId] = useState(null);
const [showAll, setShowAll] = useState(false);
@@ -81,11 +115,13 @@ export function AutomationRunsCard({ runs }: AutomationRunsCardProps) {
hasDetails && setExpandedId(isExpanded ? null : run.id)}
- role={hasDetails ? 'button' : undefined}
- style={hasDetails ? { cursor: 'pointer' } : undefined}
>
-
+
hasDetails && setExpandedId(isExpanded ? null : run.id)}
+ role={hasDetails ? 'button' : undefined}
+ style={hasDetails ? { cursor: 'pointer' } : undefined}
+ >
@@ -124,12 +160,12 @@ export function AutomationRunsCard({ runs }: AutomationRunsCardProps) {
{hasDetails && (
-
+
)}
{isExpanded && (
-
+
{run.evaluationReason && (
Evaluation
@@ -137,20 +173,10 @@ export function AutomationRunsCard({ runs }: AutomationRunsCardProps) {
)}
{run.logs && (
-
-
Logs
-
- {typeof run.logs === 'string' ? run.logs : JSON.stringify(run.logs, null, 2)}
-
-
+
)}
{run.output && (
-
-
Output
-
- {typeof run.output === 'string' ? run.output : JSON.stringify(run.output, null, 2)}
-
-
+
)}
{run.status === 'failed' && run.error && (