From 6e13d89f78dbffbc05a0bed3c56e2a001ffa4016 Mon Sep 17 00:00:00 2001 From: billlzzz10 <208072329+billlzzz10@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:51:34 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Guardian=20(JULES):=20C?= =?UTF-8?q?onsolidate=20date=20formatting=20utilities=20into=20src/lib/for?= =?UTF-8?q?mat.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .Jules/JULES.md | 1 + .Jules/guardian/2026-07-23/session-report.md | 7 + .Jules/task-log.md | 44 +++++ src/__tests__/lib/date.test.ts | 147 ----------------- src/__tests__/lib/format.test.ts | 155 +++++++++++++++++- src/app/[username]/page.tsx | 2 +- .../prompts/[id]/changes/[changeId]/page.tsx | 2 +- src/app/prompts/[id]/page.tsx | 2 +- src/components/admin/reports-table.tsx | 2 +- src/components/admin/users-table.tsx | 2 +- src/components/comments/comment-item.tsx | 2 +- src/components/layout/notification-bell.tsx | 2 +- src/components/prompts/prompt-card.tsx | 2 +- src/lib/date.ts | 34 ---- src/lib/format.ts | 41 +++++ 15 files changed, 255 insertions(+), 190 deletions(-) create mode 100644 .Jules/JULES.md create mode 100644 .Jules/guardian/2026-07-23/session-report.md create mode 100644 .Jules/task-log.md delete mode 100644 src/__tests__/lib/date.test.ts delete mode 100644 src/lib/date.ts diff --git a/.Jules/JULES.md b/.Jules/JULES.md new file mode 100644 index 00000000..993c465f --- /dev/null +++ b/.Jules/JULES.md @@ -0,0 +1 @@ +# JULES Standard diff --git a/.Jules/guardian/2026-07-23/session-report.md b/.Jules/guardian/2026-07-23/session-report.md new file mode 100644 index 00000000..4e0d6888 --- /dev/null +++ b/.Jules/guardian/2026-07-23/session-report.md @@ -0,0 +1,7 @@ +## 2026-07-23 - Consolidated Date Utilities + +**Target:** src/lib/date.ts and src/lib/format.ts +**Learning:** Found scattered date formatting utilities when they should be centralized in the format library based on JULES guidelines. Consolidating these prevents duplicate helper implementations later. +**Action:** Merged `getDateLocale`, `formatDistanceToNow`, and `formatDate` from `src/lib/date.ts` into `src/lib/format.ts`. Updated 9 downstream file imports and merged corresponding tests. +**JULES Check:** Verified no Autonomous task conflicts in `task-log.md`. +**Conflicts Avoided:** No active Autonomous tasks on date or format modules. diff --git a/.Jules/task-log.md b/.Jules/task-log.md new file mode 100644 index 00000000..43d5dacd --- /dev/null +++ b/.Jules/task-log.md @@ -0,0 +1,44 @@ +## 2026-04-16 17:04 - [GUARDIAN] Session started + +- Directory: .Jules/guardian/2026-04-16 +- Phase: PRE-FLIGHT +- JULES Check: COMPLETE + +## 2026-04-09 16:36 - [GUARDIAN] Session started + +- Directory: .Jules/guardian/2026-04-09 +- Phase: PRE-FLIGHT +- JULES Check: COMPLETE + +## 2026-04-09 17:08 - [GUARDIAN] Verification complete + +- Tests: ✓ +- Types: ✓ +- Lint: ✓ +- Build: ✓ + +## 2026-04-09 17:08 - [GUARDIAN] Consolidated formatting utilities + +- Merged `src/components/ide/utils.ts` into `src/lib/format.ts` +- Updated import in `src/components/ide/prompt-ide.tsx` +- Deleted `src/components/ide/utils.ts` + +## 2026-07-23 17:31 - [GUARDIAN] Session started + +- Directory: .Jules/guardian/2026-07-23 +- Phase: PRE-FLIGHT +- JULES Check: COMPLETE + +## 2026-07-23 17:35 - [GUARDIAN] Verification complete + +- Tests: ✓ +- Types: ✓ +- Lint: ✓ +- Build: ✓ + +## 2026-07-23 17:35 - [GUARDIAN] Consolidated date utilities + +- Merged `src/lib/date.ts` into `src/lib/format.ts` +- Updated 9 files with new import paths +- Deleted `src/lib/date.ts` +- Merged `src/__tests__/lib/date.test.ts` into `src/__tests__/lib/format.test.ts` diff --git a/src/__tests__/lib/date.test.ts b/src/__tests__/lib/date.test.ts deleted file mode 100644 index 22742d19..00000000 --- a/src/__tests__/lib/date.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { getDateLocale, formatDistanceToNow, formatDate } from "@/lib/date"; -import { enUS, tr, es, zhCN, ja, arSA } from "date-fns/locale"; - -describe("getDateLocale", () => { - it("should return enUS for 'en' locale", () => { - expect(getDateLocale("en")).toBe(enUS); - }); - - it("should return tr for 'tr' locale", () => { - expect(getDateLocale("tr")).toBe(tr); - }); - - it("should return es for 'es' locale", () => { - expect(getDateLocale("es")).toBe(es); - }); - - it("should return zhCN for 'zh' locale", () => { - expect(getDateLocale("zh")).toBe(zhCN); - }); - - it("should return ja for 'ja' locale", () => { - expect(getDateLocale("ja")).toBe(ja); - }); - - it("should return arSA for 'ar' locale", () => { - expect(getDateLocale("ar")).toBe(arSA); - }); - - it("should return enUS for unknown locale", () => { - expect(getDateLocale("unknown")).toBe(enUS); - expect(getDateLocale("fr")).toBe(enUS); - expect(getDateLocale("de")).toBe(enUS); - }); -}); - -describe("formatDistanceToNow", () => { - beforeEach(() => { - // Mock the current date to ensure consistent test results - vi.useFakeTimers(); - vi.setSystemTime(new Date("2024-01-15T12:00:00Z")); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it("should format a date object relative to now", () => { - const pastDate = new Date("2024-01-14T12:00:00Z"); - const result = formatDistanceToNow(pastDate); - expect(result).toContain("day"); - expect(result).toContain("ago"); - }); - - it("should format a date string relative to now", () => { - const pastDateString = "2024-01-14T12:00:00Z"; - const result = formatDistanceToNow(pastDateString); - expect(result).toContain("day"); - expect(result).toContain("ago"); - }); - - it("should format with different locales", () => { - const pastDate = new Date("2024-01-14T12:00:00Z"); - - // English - const enResult = formatDistanceToNow(pastDate, "en"); - expect(enResult).toContain("ago"); - - // Spanish - const esResult = formatDistanceToNow(pastDate, "es"); - expect(esResult).toContain("hace"); - - // Turkish - const trResult = formatDistanceToNow(pastDate, "tr"); - expect(trResult).toContain("önce"); - }); - - it("should handle dates from a week ago", () => { - const weekAgo = new Date("2024-01-08T12:00:00Z"); - const result = formatDistanceToNow(weekAgo); - // date-fns may return "7 days ago" instead of "1 week ago" - expect(result).toMatch(/days?|week/); - expect(result).toContain("ago"); - }); - - it("should handle dates from hours ago", () => { - const hoursAgo = new Date("2024-01-15T10:00:00Z"); - const result = formatDistanceToNow(hoursAgo); - expect(result).toContain("hours"); - expect(result).toContain("ago"); - }); - - it("should default to en locale when none provided", () => { - const pastDate = new Date("2024-01-14T12:00:00Z"); - const result = formatDistanceToNow(pastDate); - expect(result).toContain("ago"); // English suffix - }); -}); - -describe("formatDate", () => { - it("should format a date object with the given format string", () => { - const date = new Date("2024-01-15T12:30:45Z"); - expect(formatDate(date, "yyyy-MM-dd")).toBe("2024-01-15"); - }); - - it("should format a date string with the given format string", () => { - const dateString = "2024-01-15T12:30:45Z"; - expect(formatDate(dateString, "yyyy-MM-dd")).toBe("2024-01-15"); - }); - - it("should format with various format strings", () => { - const date = new Date("2024-06-15T14:30:00Z"); - - expect(formatDate(date, "MM/dd/yyyy")).toBe("06/15/2024"); - expect(formatDate(date, "dd.MM.yyyy")).toBe("15.06.2024"); - expect(formatDate(date, "MMMM d, yyyy", "en")).toBe("June 15, 2024"); - }); - - it("should format with different locales", () => { - const date = new Date("2024-06-15T14:30:00Z"); - - // English month name - const enResult = formatDate(date, "MMMM", "en"); - expect(enResult).toBe("June"); - - // Spanish month name - const esResult = formatDate(date, "MMMM", "es"); - expect(esResult).toBe("junio"); - - // Turkish month name - const trResult = formatDate(date, "MMMM", "tr"); - expect(trResult).toBe("Haziran"); - }); - - it("should handle time formatting", () => { - const date = new Date("2024-01-15T14:30:45Z"); - // Test format pattern (timezone-independent) - expect(formatDate(date, "HH:mm:ss")).toMatch(/^\d{2}:\d{2}:\d{2}$/); - expect(formatDate(date, "h:mm a", "en")).toMatch(/^\d{1,2}:\d{2} [AP]M$/i); - }); - - it("should default to en locale when none provided", () => { - const date = new Date("2024-01-15T12:00:00Z"); - const result = formatDate(date, "EEEE"); // Day of week - expect(result).toBe("Monday"); - }); -}); diff --git a/src/__tests__/lib/format.test.ts b/src/__tests__/lib/format.test.ts index 030d4bdd..0a5affd4 100644 --- a/src/__tests__/lib/format.test.ts +++ b/src/__tests__/lib/format.test.ts @@ -1,5 +1,14 @@ import { describe, it, expect } from "vitest"; -import { prettifyJson, isValidJson, toYaml } from "@/lib/format"; +import { + prettifyJson, + isValidJson, + toYaml, + getDateLocale, + formatDistanceToNow, + formatDate, +} from "@/lib/format"; +import { vi, beforeEach, afterEach } from "vitest"; +import { enUS, tr, es, zhCN, ja, arSA } from "date-fns/locale"; describe("prettifyJson", () => { it("should prettify valid JSON with proper indentation", () => { @@ -172,3 +181,147 @@ describe("toYaml", () => { expect(toYaml({ a: 1 })).toBe("a: 1"); }); }); + +describe("getDateLocale", () => { + it("should return enUS for 'en' locale", () => { + expect(getDateLocale("en")).toBe(enUS); + }); + + it("should return tr for 'tr' locale", () => { + expect(getDateLocale("tr")).toBe(tr); + }); + + it("should return es for 'es' locale", () => { + expect(getDateLocale("es")).toBe(es); + }); + + it("should return zhCN for 'zh' locale", () => { + expect(getDateLocale("zh")).toBe(zhCN); + }); + + it("should return ja for 'ja' locale", () => { + expect(getDateLocale("ja")).toBe(ja); + }); + + it("should return arSA for 'ar' locale", () => { + expect(getDateLocale("ar")).toBe(arSA); + }); + + it("should return enUS for unknown locale", () => { + expect(getDateLocale("unknown")).toBe(enUS); + expect(getDateLocale("fr")).toBe(enUS); + expect(getDateLocale("de")).toBe(enUS); + }); +}); + +describe("formatDistanceToNow", () => { + beforeEach(() => { + // Mock the current date to ensure consistent test results + vi.useFakeTimers(); + vi.setSystemTime(new Date("2024-01-15T12:00:00Z")); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should format a date object relative to now", () => { + const pastDate = new Date("2024-01-14T12:00:00Z"); + const result = formatDistanceToNow(pastDate); + expect(result).toContain("day"); + expect(result).toContain("ago"); + }); + + it("should format a date string relative to now", () => { + const pastDateString = "2024-01-14T12:00:00Z"; + const result = formatDistanceToNow(pastDateString); + expect(result).toContain("day"); + expect(result).toContain("ago"); + }); + + it("should format with different locales", () => { + const pastDate = new Date("2024-01-14T12:00:00Z"); + + // English + const enResult = formatDistanceToNow(pastDate, "en"); + expect(enResult).toContain("ago"); + + // Spanish + const esResult = formatDistanceToNow(pastDate, "es"); + expect(esResult).toContain("hace"); + + // Turkish + const trResult = formatDistanceToNow(pastDate, "tr"); + expect(trResult).toContain("önce"); + }); + + it("should handle dates from a week ago", () => { + const weekAgo = new Date("2024-01-08T12:00:00Z"); + const result = formatDistanceToNow(weekAgo); + // date-fns may return "7 days ago" instead of "1 week ago" + expect(result).toMatch(/days?|week/); + expect(result).toContain("ago"); + }); + + it("should handle dates from hours ago", () => { + const hoursAgo = new Date("2024-01-15T10:00:00Z"); + const result = formatDistanceToNow(hoursAgo); + expect(result).toContain("hours"); + expect(result).toContain("ago"); + }); + + it("should default to en locale when none provided", () => { + const pastDate = new Date("2024-01-14T12:00:00Z"); + const result = formatDistanceToNow(pastDate); + expect(result).toContain("ago"); // English suffix + }); +}); + +describe("formatDate", () => { + it("should format a date object with the given format string", () => { + const date = new Date("2024-01-15T12:30:45Z"); + expect(formatDate(date, "yyyy-MM-dd")).toBe("2024-01-15"); + }); + + it("should format a date string with the given format string", () => { + const dateString = "2024-01-15T12:30:45Z"; + expect(formatDate(dateString, "yyyy-MM-dd")).toBe("2024-01-15"); + }); + + it("should format with various format strings", () => { + const date = new Date("2024-06-15T14:30:00Z"); + + expect(formatDate(date, "MM/dd/yyyy")).toBe("06/15/2024"); + expect(formatDate(date, "dd.MM.yyyy")).toBe("15.06.2024"); + expect(formatDate(date, "MMMM d, yyyy", "en")).toBe("June 15, 2024"); + }); + + it("should format with different locales", () => { + const date = new Date("2024-06-15T14:30:00Z"); + + // English month name + const enResult = formatDate(date, "MMMM", "en"); + expect(enResult).toBe("June"); + + // Spanish month name + const esResult = formatDate(date, "MMMM", "es"); + expect(esResult).toBe("junio"); + + // Turkish month name + const trResult = formatDate(date, "MMMM", "tr"); + expect(trResult).toBe("Haziran"); + }); + + it("should handle time formatting", () => { + const date = new Date("2024-01-15T14:30:45Z"); + // Test format pattern (timezone-independent) + expect(formatDate(date, "HH:mm:ss")).toMatch(/^\d{2}:\d{2}:\d{2}$/); + expect(formatDate(date, "h:mm a", "en")).toMatch(/^\d{1,2}:\d{2} [AP]M$/i); + }); + + it("should default to en locale when none provided", () => { + const date = new Date("2024-01-15T12:00:00Z"); + const result = formatDate(date, "EEEE"); // Day of week + expect(result).toBe("Monday"); + }); +}); diff --git a/src/app/[username]/page.tsx b/src/app/[username]/page.tsx index 85e082cb..644070ab 100644 --- a/src/app/[username]/page.tsx +++ b/src/app/[username]/page.tsx @@ -2,7 +2,7 @@ import { Metadata } from "next"; import { notFound } from "next/navigation"; import Link from "next/link"; import { getTranslations, getLocale } from "next-intl/server"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; import { getPromptUrl } from "@/lib/urls"; import { Calendar, diff --git a/src/app/prompts/[id]/changes/[changeId]/page.tsx b/src/app/prompts/[id]/changes/[changeId]/page.tsx index c8428639..2e82c7cf 100644 --- a/src/app/prompts/[id]/changes/[changeId]/page.tsx +++ b/src/app/prompts/[id]/changes/[changeId]/page.tsx @@ -1,7 +1,7 @@ import { notFound } from "next/navigation"; import Link from "next/link"; import { getTranslations, getLocale } from "next-intl/server"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; import { ArrowLeft, Clock, Check, X, FileText } from "lucide-react"; import { auth } from "@/lib/auth"; import { db } from "@/lib/db"; diff --git a/src/app/prompts/[id]/page.tsx b/src/app/prompts/[id]/page.tsx index be26221e..e5043a6e 100644 --- a/src/app/prompts/[id]/page.tsx +++ b/src/app/prompts/[id]/page.tsx @@ -2,7 +2,7 @@ import { Metadata } from "next"; import { notFound } from "next/navigation"; import Link from "next/link"; import { getTranslations, getLocale } from "next-intl/server"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; import { Clock, Edit, diff --git a/src/components/admin/reports-table.tsx b/src/components/admin/reports-table.tsx index 77e9bc9e..27a4774a 100644 --- a/src/components/admin/reports-table.tsx +++ b/src/components/admin/reports-table.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import { useTranslations, useLocale } from "next-intl"; import Link from "next/link"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; import { getPromptUrl } from "@/lib/urls"; import { MoreHorizontal, Check, X, Eye, ExternalLink, RotateCcw, ListPlus } from "lucide-react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; diff --git a/src/components/admin/users-table.tsx b/src/components/admin/users-table.tsx index 5c615a94..69225443 100644 --- a/src/components/admin/users-table.tsx +++ b/src/components/admin/users-table.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { useTranslations, useLocale } from "next-intl"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; import { MoreHorizontal, Shield, diff --git a/src/components/comments/comment-item.tsx b/src/components/comments/comment-item.tsx index c11bdd7d..a7292384 100644 --- a/src/components/comments/comment-item.tsx +++ b/src/components/comments/comment-item.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useTranslations } from "next-intl"; import Link from "next/link"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; import { ChevronUp, ChevronDown, diff --git a/src/components/layout/notification-bell.tsx b/src/components/layout/notification-bell.tsx index 10d30527..a2532f1e 100644 --- a/src/components/layout/notification-bell.tsx +++ b/src/components/layout/notification-bell.tsx @@ -15,7 +15,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; interface CommentNotification { id: string; diff --git a/src/components/prompts/prompt-card.tsx b/src/components/prompts/prompt-card.tsx index 87c8bae5..d65395cf 100644 --- a/src/components/prompts/prompt-card.tsx +++ b/src/components/prompts/prompt-card.tsx @@ -4,7 +4,7 @@ import { useState, useRef, useEffect } from "react"; import Link from "next/link"; import Image from "next/image"; import { useTranslations, useLocale } from "next-intl"; -import { formatDistanceToNow } from "@/lib/date"; +import { formatDistanceToNow } from "@/lib/format"; import { getPromptUrl } from "@/lib/urls"; import { ArrowBigUp, diff --git a/src/lib/date.ts b/src/lib/date.ts deleted file mode 100644 index 85ea0371..00000000 --- a/src/lib/date.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - formatDistanceToNow as dateFnsFormatDistanceToNow, - format as dateFnsFormat, - type Locale, -} from "date-fns"; -import { enUS, tr, es, zhCN, ja, arSA } from "date-fns/locale"; - -const locales: Record = { - en: enUS, - tr: tr, - es: es, - zh: zhCN, - ja: ja, - ar: arSA, -}; - -export function getDateLocale(locale: string): Locale { - return locales[locale] || enUS; -} - -export function formatDistanceToNow(date: Date | string, locale: string = "en"): string { - const dateObj = typeof date === "string" ? new Date(date) : date; - return dateFnsFormatDistanceToNow(dateObj, { - addSuffix: true, - locale: getDateLocale(locale), - }); -} - -export function formatDate(date: Date | string, formatStr: string, locale: string = "en"): string { - const dateObj = typeof date === "string" ? new Date(date) : date; - return dateFnsFormat(dateObj, formatStr, { - locale: getDateLocale(locale), - }); -} diff --git a/src/lib/format.ts b/src/lib/format.ts index 6f4a8bac..d45841e3 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -1,3 +1,10 @@ +import { + formatDistanceToNow as dateFnsFormatDistanceToNow, + format as dateFnsFormat, + type Locale, +} from "date-fns"; +import { enUS, tr, es, zhCN, ja, arSA } from "date-fns/locale"; + /** * Prettify JSON content with proper indentation * Returns the original content if parsing fails @@ -99,3 +106,37 @@ export function toYaml(obj: unknown, indent = 0): string { return String(obj); } + +// 🛡️ Guardian: Consolidated from src/lib/date.ts (deleted) +// This function was duplicated - moved to canonical location +// JULES Check: Verified no Autonomous task conflicts +// Impact: 2 → 1 file, 30 LOC +// Date: 2026-07-23 +// Session: .Jules/guardian/2026-07-23/ +const locales: Record = { + en: enUS, + tr: tr, + es: es, + zh: zhCN, + ja: ja, + ar: arSA, +}; + +export function getDateLocale(locale: string): Locale { + return locales[locale] || enUS; +} + +export function formatDistanceToNow(date: Date | string, locale: string = "en"): string { + const dateObj = typeof date === "string" ? new Date(date) : date; + return dateFnsFormatDistanceToNow(dateObj, { + addSuffix: true, + locale: getDateLocale(locale), + }); +} + +export function formatDate(date: Date | string, formatStr: string, locale: string = "en"): string { + const dateObj = typeof date === "string" ? new Date(date) : date; + return dateFnsFormat(dateObj, formatStr, { + locale: getDateLocale(locale), + }); +}