diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index cbd9820219..e11594ef4e 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -58,7 +58,7 @@ model User {
// `CANVAS_AUTONOMOUS_TURNS_ENABLED` env var is a master kill-switch
// layered above this flag.
canvasAutonomousTurns Boolean @default(false) @map("canvas_autonomous_turns")
- dailyRecapEnabled Boolean @default(true) @map("daily_recap_enabled")
+ activityRecapEnabled Boolean @default(true) @map("daily_recap_enabled")
voiceLearningEnabled Boolean @default(false) @map("voice_learning_enabled")
// Per-user default model for the canvas Agent chat, stored in
// `getModelValue()` "provider/name" form (e.g. "anthropic/claude-sonnet-4").
diff --git a/src/__tests__/integration/api/cron/daily-recap.test.ts b/src/__tests__/integration/api/cron/daily-recap.test.ts
index aaabe0ebc2..26ff689231 100644
--- a/src/__tests__/integration/api/cron/daily-recap.test.ts
+++ b/src/__tests__/integration/api/cron/daily-recap.test.ts
@@ -13,12 +13,12 @@ import { NextRequest } from "next/server";
// ── Mocks ────────────────────────────────────────────────────────────────────
-const { mockExecuteScheduledDailyRecapRuns } = vi.hoisted(() => ({
- mockExecuteScheduledDailyRecapRuns: vi.fn(),
+const { mockExecuteScheduledActivityRecapRuns } = vi.hoisted(() => ({
+ mockExecuteScheduledActivityRecapRuns: vi.fn(),
}));
vi.mock("@/services/daily-recap-cron", () => ({
- executeScheduledDailyRecapRuns: mockExecuteScheduledDailyRecapRuns,
+ executeScheduledActivityRecapRuns: mockExecuteScheduledActivityRecapRuns,
}));
import { GET } from "@/app/api/cron/daily-recap/route";
@@ -82,14 +82,14 @@ describe("GET /api/cron/daily-recap", () => {
const body = await res.json();
expect(body.success).toBe(true);
expect(body.message).toMatch(/not configured/i);
- expect(mockExecuteScheduledDailyRecapRuns).not.toHaveBeenCalled();
+ expect(mockExecuteScheduledActivityRecapRuns).not.toHaveBeenCalled();
});
// ── Enabled ───────────────────────────────────────────────────────────────
- it("calls executeScheduledDailyRecapRuns and returns summary JSON when workflow ID is set", async () => {
+ it("calls executeScheduledActivityRecapRuns and returns summary JSON when workflow ID is set", async () => {
process.env.STAKWORK_DAILY_RECAP_WORKFLOW_ID = "12345";
- mockExecuteScheduledDailyRecapRuns.mockResolvedValue({
+ mockExecuteScheduledActivityRecapRuns.mockResolvedValue({
usersProcessed: 5,
dispatched: 4,
skipped: 1,
@@ -109,12 +109,12 @@ describe("GET /api/cron/daily-recap", () => {
errors: [],
});
expect(typeof body.timestamp).toBe("string");
- expect(mockExecuteScheduledDailyRecapRuns).toHaveBeenCalledOnce();
+ expect(mockExecuteScheduledActivityRecapRuns).toHaveBeenCalledOnce();
});
it("returns success=false when errors are present", async () => {
process.env.STAKWORK_DAILY_RECAP_WORKFLOW_ID = "12345";
- mockExecuteScheduledDailyRecapRuns.mockResolvedValue({
+ mockExecuteScheduledActivityRecapRuns.mockResolvedValue({
usersProcessed: 3,
dispatched: 2,
skipped: 0,
@@ -130,9 +130,9 @@ describe("GET /api/cron/daily-recap", () => {
expect(body.errors[0]).toMatchObject({ userId: "user-1", error: "network failure" });
});
- it("returns 500 when executeScheduledDailyRecapRuns throws", async () => {
+ it("returns 500 when executeScheduledActivityRecapRuns throws", async () => {
process.env.STAKWORK_DAILY_RECAP_WORKFLOW_ID = "12345";
- mockExecuteScheduledDailyRecapRuns.mockRejectedValue(new Error("unexpected crash"));
+ mockExecuteScheduledActivityRecapRuns.mockRejectedValue(new Error("unexpected crash"));
const res = await GET(createAuthenticatedRequest());
expect(res.status).toBe(500);
diff --git a/src/__tests__/integration/api/stakwork/daily-recap-webhook.test.ts b/src/__tests__/integration/api/stakwork/daily-recap-webhook.test.ts
index f8d2dce5cc..ab2309fd00 100644
--- a/src/__tests__/integration/api/stakwork/daily-recap-webhook.test.ts
+++ b/src/__tests__/integration/api/stakwork/daily-recap-webhook.test.ts
@@ -44,13 +44,13 @@ vi.mock("@/lib/vercel/stakwork-token", () => ({
// ─── Helpers ─────────────────────────────────────────────────────────────────
-async function createUser(opts: { dailyRecapEnabled?: boolean } = {}) {
+async function createUser(opts: { activityRecapEnabled?: boolean } = {}) {
return db.user.create({
data: {
id: generateUniqueId("user"),
email: `user-${generateUniqueId()}@test.com`,
name: "Test User",
- dailyRecapEnabled: opts.dailyRecapEnabled ?? false,
+ activityRecapEnabled: opts.activityRecapEnabled ?? false,
},
});
}
diff --git a/src/__tests__/unit/api/user/preferences.test.ts b/src/__tests__/unit/api/user/preferences.test.ts
index a197e90bc4..7b7e780ca4 100644
--- a/src/__tests__/unit/api/user/preferences.test.ts
+++ b/src/__tests__/unit/api/user/preferences.test.ts
@@ -51,7 +51,7 @@ describe("GET /api/user/preferences", () => {
canvasAutonomousTurns: true,
chatAgentModel: null,
timezone: "America/Chicago",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
});
const res = await GET();
@@ -66,7 +66,7 @@ describe("GET /api/user/preferences", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: null,
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
});
const res = await GET();
@@ -75,33 +75,33 @@ describe("GET /api/user/preferences", () => {
expect(body.timezone).toBe("UTC");
});
- test("returns dailyRecapEnabled in response", async () => {
+ test("returns activityRecapEnabled in response", async () => {
mockUserFindUnique.mockResolvedValue({
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: false,
+ activityRecapEnabled: false,
});
const res = await GET();
const body = await res.json();
expect(res.status).toBe(200);
- expect(body.dailyRecapEnabled).toBe(false);
+ expect(body.activityRecapEnabled).toBe(false);
});
- test("returns dailyRecapEnabled true by default", async () => {
+ test("returns activityRecapEnabled true by default", async () => {
mockUserFindUnique.mockResolvedValue({
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
});
const res = await GET();
const body = await res.json();
- expect(body.dailyRecapEnabled).toBe(true);
+ expect(body.activityRecapEnabled).toBe(true);
});
test("returns 401 when unauthenticated", async () => {
@@ -123,7 +123,7 @@ describe("PATCH /api/user/preferences — timezone", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "America/Chicago",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
});
const req = makeRequest({ timezone: "America/Chicago" });
@@ -161,7 +161,7 @@ describe("PATCH /api/user/preferences — timezone", () => {
canvasAutonomousTurns: true,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
});
const req = makeRequest({ canvasAutonomousTurns: true });
@@ -186,65 +186,65 @@ describe("PATCH /api/user/preferences — timezone", () => {
});
});
-describe("PATCH /api/user/preferences — dailyRecapEnabled", () => {
+describe("PATCH /api/user/preferences — activityRecapEnabled", () => {
beforeEach(() => {
vi.clearAllMocks();
mockGetServerSession.mockResolvedValue({ user: { id: "user-1" } });
});
- test("updates dailyRecapEnabled to false and returns it in the response", async () => {
+ test("updates activityRecapEnabled to false and returns it in the response", async () => {
mockUserUpdate.mockResolvedValue({
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: false,
+ activityRecapEnabled: false,
});
- const req = makeRequest({ dailyRecapEnabled: false });
+ const req = makeRequest({ activityRecapEnabled: false });
const res = await PATCH(req);
const body = await res.json();
expect(res.status).toBe(200);
- expect(body.dailyRecapEnabled).toBe(false);
+ expect(body.activityRecapEnabled).toBe(false);
expect(mockUserUpdate).toHaveBeenCalledWith(
expect.objectContaining({
- data: expect.objectContaining({ dailyRecapEnabled: false }),
+ data: expect.objectContaining({ activityRecapEnabled: false }),
}),
);
});
- test("updates dailyRecapEnabled to true", async () => {
+ test("updates activityRecapEnabled to true", async () => {
mockUserUpdate.mockResolvedValue({
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
});
- const req = makeRequest({ dailyRecapEnabled: true });
+ const req = makeRequest({ activityRecapEnabled: true });
const res = await PATCH(req);
const body = await res.json();
expect(res.status).toBe(200);
- expect(body.dailyRecapEnabled).toBe(true);
+ expect(body.activityRecapEnabled).toBe(true);
});
- test("rejects non-boolean dailyRecapEnabled with 400", async () => {
- const req = makeRequest({ dailyRecapEnabled: "yes" });
+ test("rejects non-boolean activityRecapEnabled with 400", async () => {
+ const req = makeRequest({ activityRecapEnabled: "yes" });
const res = await PATCH(req);
const body = await res.json();
expect(res.status).toBe(400);
- expect(body.error).toBe("dailyRecapEnabled must be a boolean");
+ expect(body.error).toBe("activityRecapEnabled must be a boolean");
expect(mockUserUpdate).not.toHaveBeenCalled();
});
- test("does not include dailyRecapEnabled in update when not provided", async () => {
+ test("does not include activityRecapEnabled in update when not provided", async () => {
mockUserUpdate.mockResolvedValue({
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
});
const req = makeRequest({ canvasAutonomousTurns: false });
@@ -253,7 +253,7 @@ describe("PATCH /api/user/preferences — dailyRecapEnabled", () => {
expect(res.status).toBe(200);
expect(mockUserUpdate).toHaveBeenCalledWith(
expect.objectContaining({
- data: expect.not.objectContaining({ dailyRecapEnabled: expect.anything() }),
+ data: expect.not.objectContaining({ activityRecapEnabled: expect.anything() }),
}),
);
});
@@ -280,7 +280,7 @@ describe("PATCH /api/user/preferences — voiceLearningEnabled", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
voiceLearningEnabled: true,
});
@@ -302,7 +302,7 @@ describe("PATCH /api/user/preferences — voiceLearningEnabled", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
voiceLearningEnabled: false,
});
@@ -319,7 +319,7 @@ describe("PATCH /api/user/preferences — voiceLearningEnabled", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
- dailyRecapEnabled: true,
+ activityRecapEnabled: true,
voiceLearningEnabled: false,
});
diff --git a/src/__tests__/unit/components/SidebarChat.test.tsx b/src/__tests__/unit/components/SidebarChat.test.tsx
index e8a68ad5f5..5558943744 100644
--- a/src/__tests__/unit/components/SidebarChat.test.tsx
+++ b/src/__tests__/unit/components/SidebarChat.test.tsx
@@ -164,8 +164,8 @@ vi.mock("sonner", () => ({
toast: { success: vi.fn(), error: vi.fn() },
}));
-vi.mock("@/components/daily-recap/DailyRecapCard", () => ({
- DailyRecapCard: () =>
,
+vi.mock("@/components/daily-recap/ActivityRecapCard", () => ({
+ ActivityRecapCard: () => ,
}));
vi.mock("@/components/dashboard/DashboardChat/StreamScrollIndicator", () => ({
diff --git a/src/__tests__/unit/components/daily-recap/DailyRecapCard.test.tsx b/src/__tests__/unit/components/daily-recap/ActivityRecapCard.test.tsx
similarity index 91%
rename from src/__tests__/unit/components/daily-recap/DailyRecapCard.test.tsx
rename to src/__tests__/unit/components/daily-recap/ActivityRecapCard.test.tsx
index 10154cd7a6..687d2f49ef 100644
--- a/src/__tests__/unit/components/daily-recap/DailyRecapCard.test.tsx
+++ b/src/__tests__/unit/components/daily-recap/ActivityRecapCard.test.tsx
@@ -48,7 +48,7 @@ afterEach(() => {
// ── Import after mocks ────────────────────────────────────────────────────────
-import { DailyRecapCard } from "@/components/daily-recap/DailyRecapCard";
+import { ActivityRecapCard } from "@/components/daily-recap/ActivityRecapCard";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -64,7 +64,7 @@ const GENERATED_AT = new Date().toISOString();
// ── Tests ─────────────────────────────────────────────────────────────────────
-describe("DailyRecapCard", () => {
+describe("ActivityRecapCard", () => {
beforeEach(() => {
vi.clearAllMocks();
});
@@ -73,13 +73,13 @@ describe("DailyRecapCard", () => {
it("renders null (nothing in DOM) while loading", () => {
fetchMock.mockReturnValueOnce(new Promise(() => {}));
- const { container } = render();
+ const { container } = render();
expect(container.firstChild).toBeNull();
});
it("renders null when API returns { recap: null }", async () => {
mockFetch({ recap: null, generatedAt: null });
- const { container } = render();
+ const { container } = render();
await waitFor(() => expect(fetchMock).toHaveBeenCalledOnce());
expect(container.firstChild).toBeNull();
expect(screen.queryByTestId("daily-recap-card")).toBeNull();
@@ -87,7 +87,7 @@ describe("DailyRecapCard", () => {
it("renders recap text when a recap is present", async () => {
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
expect(screen.getByText(RECAP_TEXT)).toBeInTheDocument();
});
@@ -96,7 +96,7 @@ describe("DailyRecapCard", () => {
it("heading reads \"Recap\" (not \"Daily Recap\")", async () => {
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
expect(screen.getByText("Recap")).toBeInTheDocument();
expect(screen.queryByText(/Daily Recap/i)).toBeNull();
@@ -106,7 +106,7 @@ describe("DailyRecapCard", () => {
it("shows relative timestamp without \"Generated\" prefix", async () => {
mockFetch({ recap: "Solid day.", generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
expect(screen.getByText(/2 hours ago/)).toBeInTheDocument();
expect(screen.queryByText(/Generated/)).toBeNull();
@@ -114,7 +114,7 @@ describe("DailyRecapCard", () => {
it("does not show a timestamp line when generatedAt is null", async () => {
mockFetch({ recap: "Solid day.", generatedAt: null });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
expect(screen.queryByText(/Generated/)).toBeNull();
expect(screen.queryByText(/hours ago/)).toBeNull();
@@ -124,21 +124,21 @@ describe("DailyRecapCard", () => {
it("renders X dismiss button when dismissible prop is set", async () => {
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
expect(screen.getByRole("button", { name: /dismiss/i })).toBeInTheDocument();
});
it("does not render X button when dismissible is not set", async () => {
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
expect(screen.queryByRole("button", { name: /dismiss/i })).toBeNull();
});
it("clicking X hides the card and sets sessionStorage flag", async () => {
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
fireEvent.click(screen.getByRole("button", { name: /dismiss/i }));
@@ -150,7 +150,7 @@ describe("DailyRecapCard", () => {
it("stays hidden when sessionStorage flag is pre-set", async () => {
sessionStorageMock.getItem.mockReturnValue("1");
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- const { container } = render();
+ const { container } = render();
// Give async effects time to settle
await new Promise((r) => setTimeout(r, 20));
expect(container.firstChild).toBeNull();
@@ -161,7 +161,7 @@ describe("DailyRecapCard", () => {
it("renders \"My Activity\" link to /profile when showActivityLink is set", async () => {
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
const link = screen.getByRole("link", { name: /My Activity/i });
expect(link).toBeInTheDocument();
@@ -170,7 +170,7 @@ describe("DailyRecapCard", () => {
it("does not render \"My Activity\" link when showActivityLink is not set", async () => {
mockFetch({ recap: RECAP_TEXT, generatedAt: GENERATED_AT });
- render();
+ render();
await waitFor(() => expect(screen.getByTestId("daily-recap-card")).toBeInTheDocument());
expect(screen.queryByRole("link", { name: /My Activity/i })).toBeNull();
});
@@ -179,21 +179,21 @@ describe("DailyRecapCard", () => {
it("renders null when fetch throws (network error)", async () => {
fetchMock.mockRejectedValueOnce(new Error("network down"));
- const { container } = render();
+ const { container } = render();
await new Promise((r) => setTimeout(r, 0));
expect(container.firstChild).toBeNull();
});
it("renders null when fetch returns a non-ok response", async () => {
fetchMock.mockResolvedValueOnce({ ok: false });
- const { container } = render();
+ const { container } = render();
await new Promise((r) => setTimeout(r, 0));
expect(container.firstChild).toBeNull();
});
it("calls the correct endpoint", async () => {
mockFetch({ recap: null, generatedAt: null });
- render();
+ render();
await waitFor(() => expect(fetchMock).toHaveBeenCalledOnce());
expect(fetchMock).toHaveBeenCalledWith("/api/user/daily-recap");
});
diff --git a/src/__tests__/unit/components/settings/DailyRecapSettings.test.tsx b/src/__tests__/unit/components/settings/ActivityRecapSettings.test.tsx
similarity index 62%
rename from src/__tests__/unit/components/settings/DailyRecapSettings.test.tsx
rename to src/__tests__/unit/components/settings/ActivityRecapSettings.test.tsx
index fbcee6f67a..cc36e58451 100644
--- a/src/__tests__/unit/components/settings/DailyRecapSettings.test.tsx
+++ b/src/__tests__/unit/components/settings/ActivityRecapSettings.test.tsx
@@ -2,7 +2,7 @@ import React from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import { DailyRecapSettings } from "@/components/settings/DailyRecapSettings";
+import { ActivityRecapSettings } from "@/components/settings/ActivityRecapSettings";
import { toast } from "sonner";
vi.mock("sonner");
@@ -23,7 +23,7 @@ vi.mock("@/components/ui/switch", () => ({
aria-checked={checked}
onClick={() => onCheckedChange(!checked)}
disabled={disabled}
- data-testid="daily-recap-switch"
+ data-testid="activity-recap-switch"
/>
),
}));
@@ -34,69 +34,69 @@ vi.mock("@/components/ui/label", () => ({
),
}));
-describe("DailyRecapSettings", () => {
+describe("ActivityRecapSettings", () => {
beforeEach(() => {
vi.clearAllMocks();
global.fetch = vi.fn();
});
- it("renders with toggle off when dailyRecapEnabled=false from the API", async () => {
+ it("renders with toggle off when activityRecapEnabled=false from the API", async () => {
(global.fetch as ReturnType).mockResolvedValueOnce({
ok: true,
- json: async () => ({ dailyRecapEnabled: false }),
+ json: async () => ({ activityRecapEnabled: false }),
});
- render();
+ render();
await waitFor(() => {
- const toggle = screen.getByTestId("daily-recap-switch");
+ const toggle = screen.getByTestId("activity-recap-switch");
expect(toggle).toHaveAttribute("aria-checked", "false");
});
expect(screen.getByText("Disabled")).toBeInTheDocument();
});
- it("renders with toggle on when dailyRecapEnabled=true from the API", async () => {
+ it("renders with toggle on when activityRecapEnabled=true from the API", async () => {
(global.fetch as ReturnType).mockResolvedValueOnce({
ok: true,
- json: async () => ({ dailyRecapEnabled: true }),
+ json: async () => ({ activityRecapEnabled: true }),
});
- render();
+ render();
await waitFor(() => {
- const toggle = screen.getByTestId("daily-recap-switch");
+ const toggle = screen.getByTestId("activity-recap-switch");
expect(toggle).toHaveAttribute("aria-checked", "true");
});
expect(screen.getByText("Enabled")).toBeInTheDocument();
});
- it("clicking toggle calls PATCH /api/user/preferences with { dailyRecapEnabled: true }", async () => {
+ it("clicking toggle calls PATCH /api/user/preferences with { activityRecapEnabled: true }", async () => {
const user = userEvent.setup();
(global.fetch as ReturnType)
.mockResolvedValueOnce({
ok: true,
- json: async () => ({ dailyRecapEnabled: false }),
+ json: async () => ({ activityRecapEnabled: false }),
})
.mockResolvedValueOnce({
ok: true,
- json: async () => ({ dailyRecapEnabled: true }),
+ json: async () => ({ activityRecapEnabled: true }),
});
- render();
+ render();
await waitFor(() => {
- expect(screen.getByTestId("daily-recap-switch")).not.toBeDisabled();
+ expect(screen.getByTestId("activity-recap-switch")).not.toBeDisabled();
});
- await user.click(screen.getByTestId("daily-recap-switch"));
+ await user.click(screen.getByTestId("activity-recap-switch"));
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith(
"/api/user/preferences",
expect.objectContaining({
method: "PATCH",
- body: JSON.stringify({ dailyRecapEnabled: true }),
+ body: JSON.stringify({ activityRecapEnabled: true }),
})
);
});
@@ -108,20 +108,20 @@ describe("DailyRecapSettings", () => {
(global.fetch as ReturnType)
.mockResolvedValueOnce({
ok: true,
- json: async () => ({ dailyRecapEnabled: false }),
+ json: async () => ({ activityRecapEnabled: false }),
})
.mockResolvedValueOnce({
ok: true,
- json: async () => ({ dailyRecapEnabled: true }),
+ json: async () => ({ activityRecapEnabled: true }),
});
- render();
+ render();
await waitFor(() => {
- expect(screen.getByTestId("daily-recap-switch")).not.toBeDisabled();
+ expect(screen.getByTestId("activity-recap-switch")).not.toBeDisabled();
});
- await user.click(screen.getByTestId("daily-recap-switch"));
+ await user.click(screen.getByTestId("activity-recap-switch"));
await waitFor(() => {
expect(toast.success).toHaveBeenCalledWith("Preference saved");
@@ -134,24 +134,24 @@ describe("DailyRecapSettings", () => {
(global.fetch as ReturnType)
.mockResolvedValueOnce({
ok: true,
- json: async () => ({ dailyRecapEnabled: false }),
+ json: async () => ({ activityRecapEnabled: false }),
})
.mockResolvedValueOnce({
ok: false,
json: async () => ({}),
});
- render();
+ render();
await waitFor(() => {
- expect(screen.getByTestId("daily-recap-switch")).not.toBeDisabled();
+ expect(screen.getByTestId("activity-recap-switch")).not.toBeDisabled();
});
- await user.click(screen.getByTestId("daily-recap-switch"));
+ await user.click(screen.getByTestId("activity-recap-switch"));
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith("Failed to save preference");
- expect(screen.getByTestId("daily-recap-switch")).toHaveAttribute(
+ expect(screen.getByTestId("activity-recap-switch")).toHaveAttribute(
"aria-checked",
"false"
);
diff --git a/src/__tests__/unit/services/daily-recap-cron.test.ts b/src/__tests__/unit/services/daily-recap-cron.test.ts
index 943b687a72..0a33948bd6 100644
--- a/src/__tests__/unit/services/daily-recap-cron.test.ts
+++ b/src/__tests__/unit/services/daily-recap-cron.test.ts
@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import fs from "fs";
import path from "path";
-import { executeScheduledDailyRecapRuns } from "@/services/daily-recap-cron";
+import { executeScheduledActivityRecapRuns } from "@/services/daily-recap-cron";
import { db } from "@/lib/db";
import { StakworkRunType, WorkflowStatus } from "@prisma/client";
@@ -144,9 +144,9 @@ describe("Daily Recap Cron — vercel.json configuration", () => {
});
});
-// ── executeScheduledDailyRecapRuns ───────────────────────────────────────────
+// ── executeScheduledActivityRecapRuns ───────────────────────────────────────────
-describe("executeScheduledDailyRecapRuns", () => {
+describe("executeScheduledActivityRecapRuns", () => {
beforeEach(() => {
vi.clearAllMocks();
});
@@ -155,7 +155,7 @@ describe("executeScheduledDailyRecapRuns", () => {
setupDb({ ownedWorkspace: null, membership: null });
mockedGetUserActivityFeed.mockResolvedValue([]);
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
expect(result.skipped).toBe(1);
expect(result.dispatched).toBe(0);
@@ -166,7 +166,7 @@ describe("executeScheduledDailyRecapRuns", () => {
setupDb();
mockedGetUserActivityFeed.mockResolvedValue([]);
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
expect(result.skipped).toBe(1);
expect(result.dispatched).toBe(0);
@@ -183,7 +183,7 @@ describe("executeScheduledDailyRecapRuns", () => {
},
});
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
expect(mockedDb.stakworkRun.create).toHaveBeenCalledOnce();
const createCall = vi.mocked(mockedDb.stakworkRun.create).mock.calls[0][0];
@@ -222,7 +222,7 @@ describe("executeScheduledDailyRecapRuns", () => {
});
const before = Date.now();
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
const after = Date.now();
const batchCall = mockCreateBatchProjects.mock.calls[0][0];
@@ -253,7 +253,7 @@ describe("executeScheduledDailyRecapRuns", () => {
},
});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
const batchCall = mockCreateBatchProjects.mock.calls[0][0];
const vars = batchCall[0].workflow_params.set_var.attributes.vars;
@@ -279,7 +279,7 @@ describe("executeScheduledDailyRecapRuns", () => {
},
}));
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
expect(mockCreateBatchProjects).toHaveBeenCalledTimes(2);
expect(result.usersProcessed).toBe(501);
@@ -296,7 +296,7 @@ describe("executeScheduledDailyRecapRuns", () => {
},
});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
const updateCalls = vi.mocked(mockedDb.stakworkRun.update).mock.calls;
const backFillCall = updateCalls.find((c) => c[0].data?.projectId === 777);
@@ -314,7 +314,7 @@ describe("executeScheduledDailyRecapRuns", () => {
},
});
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
// Row set to FAILED
const updateCalls = vi.mocked(mockedDb.stakworkRun.update).mock.calls;
@@ -343,7 +343,7 @@ describe("executeScheduledDailyRecapRuns", () => {
details: { body: "param is missing or the value is empty: project" },
});
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
// Both users error-logged with the real message (not '[object Object]')
expect(result.errors.length).toBe(2);
@@ -368,7 +368,7 @@ describe("Staleness reaper", () => {
setupDb();
mockedGetUserActivityFeed.mockResolvedValue([]);
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
expect(mockedDb.stakworkRun.updateMany).toHaveBeenCalledOnce();
const call = vi.mocked(mockedDb.stakworkRun.updateMany).mock.calls[0][0];
@@ -385,7 +385,7 @@ describe("Staleness reaper", () => {
mockedGetUserActivityFeed.mockResolvedValue([]);
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining("Reaped 3 stale DAILY_RECAP run(s)"),
@@ -398,7 +398,7 @@ describe("Staleness reaper", () => {
mockedGetUserActivityFeed.mockResolvedValue([]);
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
expect(warnSpy).not.toHaveBeenCalledWith(expect.stringContaining("Reaped"));
warnSpy.mockRestore();
@@ -416,7 +416,7 @@ describe("COMPLETED-only cursor", () => {
setupDb();
mockedGetUserActivityFeed.mockResolvedValue([]);
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
// First findFirst call is the cursor
const cursorCall = vi.mocked(mockedDb.stakworkRun.findFirst).mock.calls[0][0];
@@ -431,7 +431,7 @@ describe("COMPLETED-only cursor", () => {
});
const before = Date.now();
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
const after = Date.now();
// since is passed directly now (not days)
@@ -451,7 +451,7 @@ describe("COMPLETED-only cursor", () => {
data: { ref_id: "r", projects: [{ name: "daily-recap-run-1", project_id: 1 }] },
});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
const activityCall = mockedGetUserActivityFeed.mock.calls[0][0];
expect(activityCall.since).toEqual(twoDaysAgo);
@@ -465,7 +465,21 @@ describe("COMPLETED-only cursor", () => {
data: { ref_id: "r", projects: [{ name: "daily-recap-run-1", project_id: 1 }] },
});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
+
+ const activityCall = mockedGetUserActivityFeed.mock.calls[0][0];
+ expect(activityCall.since).toEqual(twoDaysAgo);
+ });
+
+ it("passes since directly to getUserActivityFeed (not days)", async () => {
+ const cursor = new Date(Date.now() - 90 * 60 * 1000); // 90 min ago
+ setupDb({ lastRun: { createdAt: cursor } });
+ mockedGetUserActivityFeed.mockResolvedValue(makeActivityItems(2));
+ mockCreateBatchProjects.mockResolvedValue({
+ data: { ref_id: "r", projects: [{ name: "daily-recap-run-1", project_id: 1 }] },
+ });
+
+ await executeScheduledActivityRecapRuns();
const activityCall = mockedGetUserActivityFeed.mock.calls[0][0];
expect(activityCall.since).toEqual(cursor);
@@ -478,7 +492,7 @@ describe("COMPLETED-only cursor", () => {
mockedGetUserActivityFeed.mockResolvedValue([]);
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("no activity since"));
logSpy.mockRestore();
@@ -497,7 +511,7 @@ describe("In-flight guard", () => {
setupDb({ inflightRun: freshRun });
mockedGetUserActivityFeed.mockResolvedValue(makeActivityItems(3));
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
expect(result.skipped).toBe(1);
expect(result.dispatched).toBe(0);
@@ -511,7 +525,7 @@ describe("In-flight guard", () => {
data: { ref_id: "r", projects: [{ name: "daily-recap-run-1", project_id: 1 }] },
});
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
expect(result.skipped).toBe(0);
expect(mockedDb.stakworkRun.create).toHaveBeenCalledOnce();
@@ -523,7 +537,7 @@ describe("In-flight guard", () => {
mockedGetUserActivityFeed.mockResolvedValue(makeActivityItems(1));
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
expect(logSpy).toHaveBeenCalledWith(
expect.stringContaining("Skipping user user-1"),
@@ -539,7 +553,7 @@ describe("In-flight guard", () => {
setupDb({ inflightRun: freshRun });
mockedGetUserActivityFeed.mockResolvedValue(makeActivityItems(3));
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
expect(mockedDb.stakworkRun.create).not.toHaveBeenCalled();
});
@@ -551,7 +565,7 @@ describe("In-flight guard", () => {
data: { ref_id: "r", projects: [{ name: "daily-recap-run-1", project_id: 1 }] },
});
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
// Guard is the 3rd findFirst call (index 2): cursor [0], lastResultRun [1], guard [2]
const guardCall = vi.mocked(mockedDb.stakworkRun.findFirst).mock.calls[2][0];
@@ -578,7 +592,7 @@ describe("window_start rolling-24h horizon", () => {
});
const before = Date.now();
- await executeScheduledDailyRecapRuns();
+ await executeScheduledActivityRecapRuns();
const after = Date.now();
const batchCall = mockCreateBatchProjects.mock.calls[0][0];
diff --git a/src/app/api/cron/daily-recap/route.ts b/src/app/api/cron/daily-recap/route.ts
index 1820ee2b51..058cb7b309 100644
--- a/src/app/api/cron/daily-recap/route.ts
+++ b/src/app/api/cron/daily-recap/route.ts
@@ -1,4 +1,4 @@
-import { executeScheduledDailyRecapRuns } from "@/services/daily-recap-cron";
+import { executeScheduledActivityRecapRuns } from "@/services/daily-recap-cron";
import { NextRequest, NextResponse } from "next/server";
/**
@@ -15,16 +15,16 @@ export async function GET(request: NextRequest) {
}
if (!process.env.STAKWORK_DAILY_RECAP_WORKFLOW_ID) {
- console.log("[DailyRecapCron] STAKWORK_DAILY_RECAP_WORKFLOW_ID not configured, skipping");
+ console.log("[ActivityRecapCron] STAKWORK_DAILY_RECAP_WORKFLOW_ID not configured, skipping");
return NextResponse.json({ success: true, message: "Daily recap cron not configured" });
}
- console.log("[DailyRecapCron] Starting scheduled execution");
+ console.log("[ActivityRecapCron] Starting scheduled execution");
- const result = await executeScheduledDailyRecapRuns();
+ const result = await executeScheduledActivityRecapRuns();
console.log(
- `[DailyRecapCron] Completed. Processed=${result.usersProcessed} ` +
+ `[ActivityRecapCron] Completed. Processed=${result.usersProcessed} ` +
`Dispatched=${result.dispatched} Skipped=${result.skipped} Errors=${result.errors.length}`,
);
@@ -39,7 +39,7 @@ export async function GET(request: NextRequest) {
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- console.error("[DailyRecapCron] Unhandled error:", errorMessage);
+ console.error("[ActivityRecapCron] Unhandled error:", errorMessage);
return NextResponse.json(
{
diff --git a/src/app/api/user/preferences/route.ts b/src/app/api/user/preferences/route.ts
index e8b84d72de..283ce82c3d 100644
--- a/src/app/api/user/preferences/route.ts
+++ b/src/app/api/user/preferences/route.ts
@@ -24,7 +24,7 @@ export async function GET() {
const user = await db.user.findUnique({
where: { id: session.user.id },
- select: { canvasAutonomousTurns: true, chatAgentModel: true, timezone: true, dailyRecapEnabled: true, voiceLearningEnabled: true },
+ select: { canvasAutonomousTurns: true, chatAgentModel: true, timezone: true, activityRecapEnabled: true, voiceLearningEnabled: true },
});
if (!user) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
@@ -34,7 +34,7 @@ export async function GET() {
canvasAutonomousTurns: user.canvasAutonomousTurns,
chatAgentModel: user.chatAgentModel,
timezone: user.timezone ?? "UTC",
- dailyRecapEnabled: user.dailyRecapEnabled,
+ activityRecapEnabled: user.activityRecapEnabled,
voiceLearningEnabled: user.voiceLearningEnabled,
});
}
@@ -48,7 +48,7 @@ export async function PATCH(request: NextRequest) {
}
const body = await request.json();
- const { canvasAutonomousTurns, chatAgentModel, timezone, dailyRecapEnabled, voiceLearningEnabled } = body;
+ const { canvasAutonomousTurns, chatAgentModel, timezone, activityRecapEnabled, voiceLearningEnabled } = body;
if (
canvasAutonomousTurns !== undefined &&
@@ -80,9 +80,9 @@ export async function PATCH(request: NextRequest) {
}
}
- if (dailyRecapEnabled !== undefined && typeof dailyRecapEnabled !== "boolean") {
+ if (activityRecapEnabled !== undefined && typeof activityRecapEnabled !== "boolean") {
return NextResponse.json(
- { error: "dailyRecapEnabled must be a boolean" },
+ { error: "activityRecapEnabled must be a boolean" },
{ status: 400 },
);
}
@@ -100,10 +100,10 @@ export async function PATCH(request: NextRequest) {
...(canvasAutonomousTurns !== undefined && { canvasAutonomousTurns }),
...(chatAgentModel !== undefined && { chatAgentModel }),
...(timezone !== undefined && { timezone }),
- ...(dailyRecapEnabled !== undefined && { dailyRecapEnabled }),
+ ...(activityRecapEnabled !== undefined && { activityRecapEnabled }),
...(voiceLearningEnabled !== undefined && { voiceLearningEnabled }),
},
- select: { canvasAutonomousTurns: true, chatAgentModel: true, timezone: true, dailyRecapEnabled: true, voiceLearningEnabled: true },
+ select: { canvasAutonomousTurns: true, chatAgentModel: true, timezone: true, activityRecapEnabled: true, voiceLearningEnabled: true },
});
logger.info("User preferences updated", "USER_PREFERENCES_UPDATE", {
@@ -111,7 +111,7 @@ export async function PATCH(request: NextRequest) {
canvasAutonomousTurns: updated.canvasAutonomousTurns,
chatAgentModel: updated.chatAgentModel,
timezone: updated.timezone,
- dailyRecapEnabled: updated.dailyRecapEnabled,
+ activityRecapEnabled: updated.activityRecapEnabled,
voiceLearningEnabled: updated.voiceLearningEnabled,
});
@@ -119,7 +119,7 @@ export async function PATCH(request: NextRequest) {
canvasAutonomousTurns: updated.canvasAutonomousTurns,
chatAgentModel: updated.chatAgentModel,
timezone: updated.timezone ?? "UTC",
- dailyRecapEnabled: updated.dailyRecapEnabled,
+ activityRecapEnabled: updated.activityRecapEnabled,
voiceLearningEnabled: updated.voiceLearningEnabled,
});
} catch (error) {
diff --git a/src/app/org/[githubLogin]/_components/SidebarChat.tsx b/src/app/org/[githubLogin]/_components/SidebarChat.tsx
index 2dc3b60735..b7499b89dc 100644
--- a/src/app/org/[githubLogin]/_components/SidebarChat.tsx
+++ b/src/app/org/[githubLogin]/_components/SidebarChat.tsx
@@ -39,7 +39,7 @@ import {
import { PlannerFormSlot } from "./PlannerFormSlot";
import { StartTasksSlot } from "./StartTasksSlot";
import { DeferredCheckCard } from "./DeferredCheckCard";
-import { DailyRecapCard } from "@/components/daily-recap/DailyRecapCard";
+import { ActivityRecapCard } from "@/components/daily-recap/ActivityRecapCard";
import {
useCanvasChatStore,
@@ -343,7 +343,7 @@ export function SidebarChat({ githubLogin }: SidebarChatProps) {
-
+
{!hasMessages && activeToolCalls.length === 0 && (
Ask the agent about anything on this canvas.
diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx
index ff581fa632..00a624fc8d 100644
--- a/src/app/profile/page.tsx
+++ b/src/app/profile/page.tsx
@@ -2,10 +2,10 @@ import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth/nextauth";
import { redirect } from "next/navigation";
import Image from "next/image";
+import Link from "next/link";
import { ActivityFeed } from "./_components/ActivityFeed";
import { BackButton } from "./_components/BackButton";
-import { DailyRecapCard } from "@/components/daily-recap/DailyRecapCard";
-import { DailyRecapSettings } from "@/components/settings/DailyRecapSettings";
+import { ActivityRecapCard } from "@/components/daily-recap/ActivityRecapCard";
export default async function ProfilePage() {
const session = await getServerSession(authOptions);
@@ -51,9 +51,14 @@ export default async function ProfilePage() {
-
-
-
+
+
+
+ Manage Activity Recap in Settings →
+
diff --git a/src/components/daily-recap/DailyRecapCard.tsx b/src/components/daily-recap/ActivityRecapCard.tsx
similarity index 91%
rename from src/components/daily-recap/DailyRecapCard.tsx
rename to src/components/daily-recap/ActivityRecapCard.tsx
index 6c7ebc08de..3bdb3a7450 100644
--- a/src/components/daily-recap/DailyRecapCard.tsx
+++ b/src/components/daily-recap/ActivityRecapCard.tsx
@@ -5,12 +5,12 @@ import { formatDistanceToNow } from "date-fns";
import { Sparkles, X } from "lucide-react";
import Link from "next/link";
-interface DailyRecapData {
+interface ActivityRecapData {
recap: string | null;
generatedAt: string | null;
}
-interface DailyRecapCardProps {
+interface ActivityRecapCardProps {
/** When true, renders an X button that hides the card for the session. */
dismissible?: boolean;
/** When true, renders a "My Activity →" link to /profile. */
@@ -24,8 +24,8 @@ const SESSION_KEY = "hive:daily-recap-dismissed";
* Fetches GET /api/user/daily-recap on mount.
* Returns null while loading or when no completed recap exists.
*/
-export function DailyRecapCard({ dismissible, showActivityLink }: DailyRecapCardProps = {}) {
- const [data, setData] = useState
(null);
+export function ActivityRecapCard({ dismissible, showActivityLink }: ActivityRecapCardProps = {}) {
+ const [data, setData] = useState(null);
const [dismissed, setDismissed] = useState(false);
// Check session-dismissed flag on mount (dismissible mode only).
@@ -43,7 +43,7 @@ export function DailyRecapCard({ dismissible, showActivityLink }: DailyRecapCard
useEffect(() => {
fetch("/api/user/daily-recap")
.then((r) => (r.ok ? r.json() : null))
- .then((json: DailyRecapData | null) => {
+ .then((json: ActivityRecapData | null) => {
if (json?.recap) setData(json);
})
.catch(() => {/* silent — card simply doesn't render */});
diff --git a/src/components/settings/DailyRecapSettings.tsx b/src/components/settings/ActivityRecapSettings.tsx
similarity index 73%
rename from src/components/settings/DailyRecapSettings.tsx
rename to src/components/settings/ActivityRecapSettings.tsx
index 224a68aea2..6ace7222ad 100644
--- a/src/components/settings/DailyRecapSettings.tsx
+++ b/src/components/settings/ActivityRecapSettings.tsx
@@ -12,7 +12,7 @@ import {
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
-export function DailyRecapSettings() {
+export function ActivityRecapSettings() {
const [enabled, setEnabled] = useState(false);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -21,7 +21,7 @@ export function DailyRecapSettings() {
fetch("/api/user/preferences")
.then((r) => r.json())
.then((d) => {
- setEnabled(!!d.dailyRecapEnabled);
+ setEnabled(!!d.activityRecapEnabled);
})
.catch(() => {})
.finally(() => setLoading(false));
@@ -36,7 +36,7 @@ export function DailyRecapSettings() {
const res = await fetch("/api/user/preferences", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ dailyRecapEnabled: newValue }),
+ body: JSON.stringify({ activityRecapEnabled: newValue }),
});
if (!res.ok) throw new Error("Failed");
@@ -53,23 +53,22 @@ export function DailyRecapSettings() {
return (
- Daily Recap
+ Activity Recap
- When enabled, you will receive a daily AI-generated recap of your
- activity across your workspaces. The recap is delivered each morning
- and surfaced here on your profile. Applies across all your workspaces.
+ When enabled, you will receive an AI-generated recap of your activity
+ across your workspaces. Applies across all your workspaces.
-
diff --git a/src/services/daily-recap-cron.ts b/src/services/daily-recap-cron.ts
index a78a80acc8..2721b2bc71 100644
--- a/src/services/daily-recap-cron.ts
+++ b/src/services/daily-recap-cron.ts
@@ -41,7 +41,7 @@ function chunkArray(arr: T[], size: number): T[][] {
* 5. Batch-dispatch via POST /api/v1/projects/batch (≤500 per chunk).
* 6. Back-fill projectId / status on the rows.
*/
-export async function executeScheduledDailyRecapRuns(): Promise {
+export async function executeScheduledActivityRecapRuns(): Promise {
const result: DailyRecapCronResult = {
usersProcessed: 0,
dispatched: 0,
@@ -51,13 +51,13 @@ export async function executeScheduledDailyRecapRuns(): Promise 0) {
- console.warn(`[DailyRecapCron] Reaped ${reaped.count} stale DAILY_RECAP run(s) → FAILED`);
+ console.warn(`[ActivityRecapCron] Reaped ${reaped.count} stale DAILY_RECAP run(s) → FAILED`);
}
// ── 1. Query eligible users ──────────────────────────────────────────────
const users = await db.user.findMany({
- where: { dailyRecapEnabled: true, deleted: false },
+ where: { activityRecapEnabled: true, deleted: false },
select: { id: true },
});
- console.log(`[DailyRecapCron] Found ${users.length} eligible user(s)`);
+ console.log(`[ActivityRecapCron] Found ${users.length} eligible user(s)`);
result.usersProcessed = users.length;
// Pending runs to batch-dispatch
@@ -117,7 +117,7 @@ export async function executeScheduledDailyRecapRuns(): Promise p.project_id).length} succeeded, ` +
`${projects.filter((p) => !p.project_id).length} failed`,
);
@@ -270,7 +270,7 @@ export async function executeScheduledDailyRecapRuns(): Promise