Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b9daa3b
Generated with Hive: Rebrand Daily Recap to Activity Recap in compone…
tomsmith8 Jul 3, 2026
fc78fb3
Merge origin/master: resolve conflict keeping ActivityRecapCard
tomsmith8 Jul 3, 2026
bccc5cd
Generated with Hive: Fix ESLint warnings and errors from CI lint check
tomsmith8 Jul 3, 2026
e33a3ac
Generated with Hive: Update SidebarChat tests to mock ActivityRecapCa…
tomsmith8 Jul 3, 2026
5440dce
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 3, 2026
2358ab6
Carry forward: rework daily recap cron to activity recap with increme…
tomsmith8 Jul 3, 2026
642cbeb
Merge remote-tracking branch 'origin/master' into HEAD
tomsmith8 Jul 3, 2026
a35214e
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
26decd9
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
931ff1b
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
64f968e
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
251aca4
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
f09e2ff
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
8d9d5ef
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
61c859a
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
6578754
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
96ec893
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
2edfd8f
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
9ecfe34
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
e1176f8
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
85169fa
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
c726651
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
f9d7630
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
cca9a59
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
11e531e
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
38fef5e
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 4, 2026
43d5c4f
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
69d3ff3
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
c6f0b1a
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
4081fec
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
d1b9bf8
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
2596449
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
8abc700
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
62828c5
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
3233b58
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
9e17d98
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
d6a6906
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
4d4f1af
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
8d34ffb
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
ee20a7e
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
0b3fcf3
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
6efcadc
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
29d4837
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
c6ad60f
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
36bad9d
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
7e36ddd
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
3a9869f
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 5, 2026
26c449c
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 6, 2026
c04ba77
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 6, 2026
eaaffef
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 6, 2026
ae796ba
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 6, 2026
b678ebb
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 6, 2026
7640cba
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 6, 2026
90949cd
Merge branch 'master' into feature/CMR4QPO1R000EL404SQ99YBSW-rebrand-…
tomsmith8 Jul 6, 2026
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
2 changes: 1 addition & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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").
Expand Down
20 changes: 10 additions & 10 deletions src/__tests__/integration/api/cron/daily-recap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
});
}
Expand Down
58 changes: 29 additions & 29 deletions src/__tests__/unit/api/user/preferences.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe("GET /api/user/preferences", () => {
canvasAutonomousTurns: true,
chatAgentModel: null,
timezone: "America/Chicago",
dailyRecapEnabled: true,
activityRecapEnabled: true,
});

const res = await GET();
Expand All @@ -66,7 +66,7 @@ describe("GET /api/user/preferences", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: null,
dailyRecapEnabled: true,
activityRecapEnabled: true,
});

const res = await GET();
Expand All @@ -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 () => {
Expand All @@ -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" });
Expand Down Expand Up @@ -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 });
Expand All @@ -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 });
Expand All @@ -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() }),
}),
);
});
Expand All @@ -280,7 +280,7 @@ describe("PATCH /api/user/preferences — voiceLearningEnabled", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
dailyRecapEnabled: true,
activityRecapEnabled: true,
voiceLearningEnabled: true,
});

Expand All @@ -302,7 +302,7 @@ describe("PATCH /api/user/preferences — voiceLearningEnabled", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
dailyRecapEnabled: true,
activityRecapEnabled: true,
voiceLearningEnabled: false,
});

Expand All @@ -319,7 +319,7 @@ describe("PATCH /api/user/preferences — voiceLearningEnabled", () => {
canvasAutonomousTurns: false,
chatAgentModel: null,
timezone: "UTC",
dailyRecapEnabled: true,
activityRecapEnabled: true,
voiceLearningEnabled: false,
});

Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/unit/components/SidebarChat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ vi.mock("sonner", () => ({
toast: { success: vi.fn(), error: vi.fn() },
}));

vi.mock("@/components/daily-recap/DailyRecapCard", () => ({
DailyRecapCard: () => <div data-testid="daily-recap-card" />,
vi.mock("@/components/daily-recap/ActivityRecapCard", () => ({
ActivityRecapCard: () => <div data-testid="daily-recap-card" />,
}));

vi.mock("@/components/dashboard/DashboardChat/StreamScrollIndicator", () => ({
Expand Down
Loading
Loading