fix(coach): count the daily question limit from the person's midnight

The limit on clarifying questions reset at UTC midnight, which is the middle
of the night in Europe and the afternoon on the American west coast. It now
counts from the start of the person's own day.
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 09:58:02 +02:00
1 parent f4262e2cc6
commit 2372fee376
2 files changed
+36 -3

No files matched your search

@@ -229,6 +229,32 @@ describe("clarificationAllowed", () => {
).resolves.toBe(false);
});
it("counts the day in the person's zone, not from UTC midnight", async () => {
// 23:30 UTC on 1 March is already 2 March in Auckland and still 1 March
// in Los Angeles; each day starts at its own local midnight.
const now = new Date("2026-03-01T23:30:00.000Z");
await clarificationAllowed({
userId: "u1",
conversationId: "c1",
now,
tz: "Pacific/Auckland",
});
await clarificationAllowed({
userId: "u1",
conversationId: "c1",
now,
tz: "America/Los_Angeles",
});
const since = db.messageCount.mock.calls.map(
([q]) =>
(q as { where: { createdAt: { gte: Date } } }).where.createdAt.gte,
);
expect(since).toEqual([
new Date("2026-03-01T11:00:00.000Z"),
new Date("2026-03-01T08:00:00.000Z"),
]);
});
it("refuses when the count cannot be read", async () => {
db.messageFindMany.mockRejectedValue(new Error("down"));
await expect(
+10 -3
View File
@@ -30,6 +30,9 @@ import {
type LatestMessagesLoader,
} from "@/lib/ai/coach/latest-messages";
import { prisma } from "@/lib/db";
import { startOfLocalDayKey } from "@/lib/tz/local-day";
import { userDayKey } from "@/lib/tz/format";
import { resolveUserTimezone } from "@/lib/tz/resolver";
import { annotate } from "@/lib/logging/context";
import type { Locale } from "@/lib/i18n/config";
import { resolveIntlLocale } from "@/lib/format-locale";
@@ -704,6 +707,8 @@ export async function clarificationAllowed(args: {
userId: string;
conversationId: string;
now?: Date;
/** The person's zone; resolved from the account when absent. */
tz?: string;
}): Promise<boolean> {
const now = args.now ?? new Date();
try {
@@ -726,9 +731,11 @@ export async function clarificationAllowed(args: {
) {
return false;
}
const dayStart = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()),
);
// The person's day, not the server's: a limit that reset at UTC
// midnight reset at one or two in the morning in Berlin and in the
// afternoon in the Pacific.
const tz = args.tz ?? (await resolveUserTimezone(args.userId));
const dayStart = startOfLocalDayKey(userDayKey(now, tz), tz);
const today = await prisma.coachMessage.count({
where: {
conversation: { userId: args.userId },