Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { SettingCard } from "@/components/SettingCard";
import {
Expand All @@ -13,12 +14,14 @@ import {
import { AboutSection } from "@/app/(app)/[emailAccountId]/settings/AboutSectionForm";

export function AboutSetting() {
const [open, setOpen] = useState(false);

return (
<SettingCard
title="About you"
description="Provide extra information that will help our AI better understand how to process your emails."
right={
<Dialog>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
Edit
Expand All @@ -33,7 +36,7 @@ export function AboutSetting() {
</DialogDescription>
</DialogHeader>

<AboutSection />
<AboutSection onSuccess={() => setOpen(false)} />
</DialogContent>
</Dialog>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { SettingCard } from "@/components/SettingCard";
import {
Expand All @@ -13,12 +14,14 @@ import {
import { DigestSettingsForm } from "@/app/(app)/[emailAccountId]/settings/DigestSettingsForm";

export function DigestSetting() {
const [open, setOpen] = useState(false);

return (
<SettingCard
title="Digest"
description="Configure your summary digest emails."
right={
<Dialog>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
Edit
Expand All @@ -33,7 +36,7 @@ export function DigestSetting() {
</DialogDescription>
</DialogHeader>

<DigestSettingsForm />
<DigestSettingsForm onSuccess={() => setOpen(false)} />
</DialogContent>
</Dialog>
}
Expand Down
10 changes: 5 additions & 5 deletions apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
TypographyH3,
} from "@/components/Typography";
import { ConnectCalendar } from "@/app/(app)/[emailAccountId]/calendars/ConnectCalendar";
import { UserIcon, MailIcon, LightbulbIcon } from "lucide-react";
import { MailIcon, LightbulbIcon, UserSearchIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Item,
Expand Down Expand Up @@ -47,9 +47,9 @@ export function BriefsOnboarding({
</SectionDescription>
</div>

<ItemGroup className="grid mt-6">
<ItemGroup className="mt-6">
<Item>
<UserIcon className="text-blue-500" />
<UserSearchIcon className="text-blue-500 size-4" />
<ItemContent>
<ItemTitle>Attendee research</ItemTitle>
<ItemDescription>
Expand All @@ -58,7 +58,7 @@ export function BriefsOnboarding({
</ItemContent>
</Item>
<Item>
<MailIcon className="text-blue-500" />
<MailIcon className="text-blue-500 size-4" />
<ItemContent>
<ItemTitle>Email history</ItemTitle>
<ItemDescription>
Expand All @@ -67,7 +67,7 @@ export function BriefsOnboarding({
</ItemContent>
</Item>
<Item>
<LightbulbIcon className="text-blue-500" />
<LightbulbIcon className="text-blue-500 size-4" />
<ItemContent>
<ItemTitle>Key context</ItemTitle>
<ItemDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function AboutSectionFull() {
);
}

export function AboutSection() {
export function AboutSection({ onSuccess }: { onSuccess?: () => void }) {
const { data, isLoading, error, mutate } = useEmailAccountFull();

return (
Expand All @@ -48,17 +48,23 @@ export function AboutSection() {
error={error}
loadingComponent={<Skeleton className="h-32 w-full" />}
>
<AboutSectionForm about={data?.about ?? null} mutate={mutate} />
<AboutSectionForm
about={data?.about ?? null}
mutate={mutate}
onSuccess={onSuccess}
/>
</LoadingContent>
);
}

const AboutSectionForm = ({
about,
mutate,
onSuccess,
}: {
about: string | null;
mutate: () => void;
onSuccess?: () => void;
}) => {
const {
register,
Expand All @@ -78,6 +84,7 @@ const AboutSectionForm = ({
toastSuccess({
description: "Your profile has been updated!",
});
onSuccess?.();
},
onError: (error) => {
toastError({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const daysOfWeek = [
{ value: "6", label: "Saturday" },
];

export function DigestSettingsForm() {
export function DigestSettingsForm({ onSuccess }: { onSuccess?: () => void }) {
const { emailAccountId } = useAccount();
const {
data: rules,
Expand Down Expand Up @@ -224,14 +224,15 @@ export function DigestSettingsForm() {
toastSuccess({
description: "Your digest settings have been updated!",
});
onSuccess?.();
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing onSuccess in useCallback dependency array. The onSuccess prop is used inside the onSubmit callback but is not listed in the dependency array [rules, executeItems, executeSchedule]. This will cause stale closure issues if the parent component provides a different onSuccess callback on re-render.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/(app)/[emailAccountId]/settings/DigestSettingsForm.tsx, line 227:

<comment>Missing `onSuccess` in `useCallback` dependency array. The `onSuccess` prop is used inside the `onSubmit` callback but is not listed in the dependency array `[rules, executeItems, executeSchedule]`. This will cause stale closure issues if the parent component provides a different `onSuccess` callback on re-render.</comment>

<file context>
@@ -224,6 +224,7 @@ export function DigestSettingsForm() {
         toastSuccess({
           description: &quot;Your digest settings have been updated!&quot;,
         });
+        onSuccess?.();
       } catch {
         toastError({
</file context>

✅ Addressed in 3ea9607

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 3ea9607 addressed this comment by adding onSuccess to the useCallback dependency array. The dependency array was updated from [rules, executeItems, executeSchedule] to [rules, executeItems, executeSchedule, onSuccess], which resolves the stale closure issue that was identified.

} catch {
toastError({
title: "Error updating digest settings",
description: "An error occurred while saving your settings",
});
}
},
[rules, executeItems, executeSchedule],
[rules, executeItems, executeSchedule, onSuccess],
);

// Create options for MultiSelectFilter
Expand Down
132 changes: 132 additions & 0 deletions apps/web/utils/ai/meeting-briefs/generate-briefing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { describe, it, expect, vi } from "vitest";
import type { MeetingBriefingData } from "@/utils/meeting-briefs/gather-context";

vi.mock("server-only", () => ({}));
vi.mock("@/utils/llms/model", () => ({ getModel: vi.fn() }));
vi.mock("@/utils/llms", () => ({ createGenerateObject: vi.fn() }));
vi.mock("@/utils/stringify-email", () => ({
stringifyEmailSimple: vi.fn(
(email) =>
`From: ${email.from}\nSubject: ${email.subject}\nBody: ${email.content}`,
),
}));
vi.mock("@/utils/get-email-from-message", () => ({
getEmailForLLM: vi.fn((msg) => ({
from: msg.headers?.from || "unknown",
subject: msg.headers?.subject || "no subject",
content: msg.textPlain || "no content",
})),
}));

vi.doUnmock("@/utils/date");

import { buildPrompt } from "./generate-briefing";

describe("buildPrompt timezone handling", () => {
it("formats past meeting times in the user's timezone (not UTC)", () => {
// This test documents the timezone bug fix:
// - Calendar API stores times in UTC
// - A 4 PM BRT meeting is stored as 7 PM UTC
// - The prompt should show 4 PM (user's local time), not 7 PM (UTC)

const meetingAt4pmBRT = new Date("2024-12-30T19:00:00Z"); // 7 PM UTC = 4 PM BRT

const briefingData: MeetingBriefingData = {
event: {
id: "upcoming",
title: "Strategy Review",
description: "Discuss Q1 roadmap",
startTime: new Date("2024-12-31T21:00:00Z"),
endTime: new Date("2024-12-31T22:00:00Z"),
attendees: [
{ email: "user@company.com" },
{ email: "client@acme.com", name: "John Smith" },
],
},
externalGuests: [{ email: "client@acme.com", name: "John Smith" }],
emailThreads: [],
pastMeetings: [
{
id: "past-1",
title: "Previous Call",
description: "Discussed partnership opportunities",
startTime: meetingAt4pmBRT,
endTime: new Date("2024-12-30T20:00:00Z"),
attendees: [{ email: "client@acme.com", name: "John Smith" }],
},
],
};

const prompt = buildPrompt(briefingData, "America/Sao_Paulo");

// The past meeting should show "4:00 PM" (Brazil time), NOT "7:00 PM" (UTC)
expect(prompt).toMatchInlineSnapshot(`
"Please prepare a concise briefing for this meeting.

<upcoming_meeting>
Title: Strategy Review
Description: Discuss Q1 roadmap
</upcoming_meeting>

<guest_context>
<guest>
Name: John Smith
Email: client@acme.com

<recent_meetings>
<meeting>
Title: Previous Call
Date: Dec 30, 2024 at 4:00 PM
Description: Discussed partnership opportunities
</meeting>

</recent_meetings>
</guest>

</guest_context>

Return the briefing as a JSON object with a "guests" array containing structured information for each guest."
`);
});

it("shows no prior context for new contacts", () => {
const briefingData: MeetingBriefingData = {
event: {
id: "upcoming",
title: "Intro Meeting",
startTime: new Date("2024-12-31T21:00:00Z"),
endTime: new Date("2024-12-31T22:00:00Z"),
attendees: [
{ email: "user@company.com" },
{ email: "newcontact@other.com", name: "New Person" },
],
},
externalGuests: [{ email: "newcontact@other.com", name: "New Person" }],
emailThreads: [],
pastMeetings: [],
};

const prompt = buildPrompt(briefingData, "America/Sao_Paulo");

expect(prompt).toMatchInlineSnapshot(`
"Please prepare a concise briefing for this meeting.

<upcoming_meeting>
Title: Intro Meeting

</upcoming_meeting>

<guest_context>
<guest>
Name: New Person
Email: newcontact@other.com

<no_prior_context>This appears to be a new contact with no prior email, meeting, or public profile history.</no_prior_context>
</guest>

</guest_context>

Return the briefing as a JSON object with a "guests" array containing structured information for each guest."
`);
});
});
Loading
Loading