Add spec compliance improvements: seed switch alert, calendar emojis, period indicator, IP logging
Some checks failed
CI / quality (push) Failing after 28s
Deploy / deploy (push) Successful in 2m38s

- NutritionPanel: Display seed switch alert on day 15 per dashboard spec
- MonthView: Add phase emojis to legend (🩸🌱🌸🌙🌑) per calendar spec
- DayCell: Show period indicator (🩸) for days 1-3 per calendar spec
- Auth middleware: Log client IP from x-forwarded-for/x-real-ip per observability spec
- Updated NutritionGuidance type to include seedSwitchAlert field
- /api/today now returns seedSwitchAlert in nutrition response

Test coverage: 1005 tests (15 new tests added)
- nutrition-panel.test.tsx: +4 tests
- month-view.test.tsx: +1 test
- day-cell.test.tsx: +5 tests
- auth-middleware.test.ts: +3 tests
- today/route.test.ts: +2 tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-12 23:33:14 +00:00
parent d613417e47
commit eeeece17bf
12 changed files with 293 additions and 48 deletions

View File

@@ -456,6 +456,40 @@ describe("GET /api/today", () => {
);
expect(body.nutrition.carbRange).toBe("75-125g");
});
it("returns seed switch alert on day 15", async () => {
// Set to cycle day 15 - the seed switch day
currentMockUser = createMockUser({
lastPeriodDate: new Date("2024-12-27"), // 14 days ago = day 15
});
currentMockDailyLog = createMockDailyLog({
cycleDay: 15,
phase: "OVULATION",
});
const response = await GET(mockRequest);
expect(response.status).toBe(200);
const body = await response.json();
expect(body.nutrition.seedSwitchAlert).toBe(
"🌱 SWITCH TODAY! Start Sesame + Sunflower",
);
});
it("returns null seed switch alert on other days", async () => {
currentMockUser = createMockUser({
lastPeriodDate: new Date("2025-01-01"), // cycle day 10
});
currentMockDailyLog = createMockDailyLog();
const response = await GET(mockRequest);
expect(response.status).toBe(200);
const body = await response.json();
expect(body.nutrition.seedSwitchAlert).toBeNull();
});
});
describe("biometrics data", () => {

View File

@@ -11,7 +11,7 @@ import {
} from "@/lib/cycle";
import { getDecisionWithOverrides } from "@/lib/decision-engine";
import { logger } from "@/lib/logger";
import { getNutritionGuidance } from "@/lib/nutrition";
import { getNutritionGuidance, getSeedSwitchAlert } from "@/lib/nutrition";
import type { DailyData, DailyLog, HrvStatus } from "@/types";
// Default biometrics when no Garmin data is available
@@ -107,8 +107,12 @@ export const GET = withAuth(async (_request, user, pb) => {
"Decision calculated",
);
// Get nutrition guidance
const nutrition = getNutritionGuidance(cycleDay);
// Get nutrition guidance with seed switch alert
const baseNutrition = getNutritionGuidance(cycleDay);
const nutrition = {
...baseNutrition,
seedSwitchAlert: getSeedSwitchAlert(cycleDay),
};
return NextResponse.json({
decision,