diff --git a/CHANGELOG.md b/CHANGELOG.md index d524eec..b6524d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Archive Edit Dialog**: 4 time pickers (2 for day start/end, 2 for task start/end) - Removed duplicate `generateTimeOptions()` helper functions from all dialog components +### Removed +- Deleted `src/hooks/.useReportSummary-Claude.ts` — unused development dotfile that contained a direct Anthropic API key reference +- Deleted `src/utils/supabase.ts` — duplicate Supabase client that was never imported, creating a redundant connection +- Deleted `src/hooks/useRealtimeSync.ts` — hook whose body was fully disabled (`return;` / `return () => {}`) and was only referenced in a commented-out call +- Deleted `src/hooks/useOffline.tsx` — context accessor hook that was never called anywhere in the application +- Removed `saveCurrentDayRef` from `TimeTrackingContext` — a `useRef` that was declared but never assigned +- Removed `addToQueue`, `offlineQueue`, `processQueue`, and `SYNC_REQUIRED_EVENT` from `OfflineContext` — the offline action queue was implemented but `addToQueue` was never called, so the queue was permanently empty and the sync event was never dispatched; `OfflineContext` now exposes only `isOnline` with online/offline toast notifications + ### Fixed +- Fixed online reconnection never triggering a backend sync for authenticated users + — `src/contexts/TimeTrackingContext.tsx` (added native `online` window event listener that calls `forceSyncToDatabase()` when authenticated; the previous offline-queue mechanism was broken because the queue was never populated, so reconnection silently dropped any pending changes) +- Fixed `trackAuthCall` stub recording no log details + — `src/lib/supabase.ts` (the function incremented `authCallCount` but created a `timestamp` variable it never used and pushed nothing to `dbCallLog`, making auth call debugging impossible; now writes a full log entry matching `trackDbCall` so `getDbCallStats()` shows auth calls alongside DB calls) +- Fixed `getCurrentDay()` crashing and silently discarding the stored day when the `tasks` field is absent from a localStorage record + — `src/services/localStorageService.ts` (`data.tasks.map()` threw a TypeError on a missing/null field; replaced with `(data.tasks ?? []).map()` so a corrupt or partial record produces an empty task list instead of swallowing the entire day) - Improved Weekly Report error messages to distinguish between distinct Gemini API failure modes — `src/hooks/useReportSummary.ts` (added `classifyGeminiError()` and `classifyFinishReason()` helpers; 429 rate limit vs. quota exhaustion, 503 overload, 500 server error, 504 timeout, 403 bad key, 400 precondition, 404 model not found, network failures, and content-blocked responses each produce specific actionable messages instead of Gemini's generic "high demand" text) - Fixed Weekly Report page only reading archived data from localStorage, causing empty reports for authenticated (Supabase) users @@ -55,7 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mobile-optimized interface with touch navigation - Dark mode support - Authentication via Supabase (optional) -- Real-time sync across devices (when authenticated) +- Cloud sync across devices via Supabase (when authenticated) --- diff --git a/agents/architecture.md b/agents/architecture.md index 6ffda21..aeea3f2 100644 --- a/agents/architecture.md +++ b/agents/architecture.md @@ -58,7 +58,6 @@ Complex logic is extracted into custom hooks: - `useAuth()` — Access authentication state and methods - `useTimeTracking()` — Access time tracking state and methods -- `useRealtimeSync()` — Real-time sync (currently disabled) ### 4. Lazy Loading Pattern diff --git a/package-lock.json b/package-lock.json index c08d499..9384244 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4187,7 +4187,7 @@ "version": "1.13.5", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4229,6 +4229,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4245,6 +4246,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4261,6 +4263,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4277,6 +4280,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4293,6 +4297,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4309,6 +4314,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4325,6 +4331,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4341,6 +4348,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4357,6 +4365,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4373,6 +4382,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4386,14 +4396,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.25", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" diff --git a/src/components/ArchiveEditDialog.tsx b/src/components/ArchiveEditDialog.tsx index 2108b6e..54126ed 100644 --- a/src/components/ArchiveEditDialog.tsx +++ b/src/components/ArchiveEditDialog.tsx @@ -13,13 +13,6 @@ import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { MarkdownDisplay } from '@/components/MarkdownDisplay'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue -} from '@/components/ui/select'; import { TimePicker } from '@/components/ui/scroll-time-picker'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { @@ -42,6 +35,7 @@ import { import { formatDuration, formatDate } from '@/utils/timeUtil'; import { DayRecord, Task } from '@/contexts/TimeTrackingContext'; import { useTimeTracking } from '@/hooks/useTimeTracking'; +import { TaskEditInArchiveDialog } from '@/components/TaskEditInArchiveDialog'; interface ArchiveEditDialogProps { day: DayRecord; @@ -596,254 +590,3 @@ export const ArchiveEditDialog: React.FC = ({ ); }; -// Separate component for editing tasks within archived days -interface TaskEditInArchiveDialogProps { - task: Task; - isOpen: boolean; - onClose: () => void; - onSave: (task: Task) => void; -} - -const TaskEditInArchiveDialog: React.FC = ({ - task, - isOpen, - onClose, - onSave -}) => { - const { projects, categories } = useTimeTracking(); - const [formData, setFormData] = useState({ - title: '', - description: '', - project: 'none', - category: 'none' - }); - - const [timeData, setTimeData] = useState({ - startTime: '', - endTime: '' - }); - - useEffect(() => { - if (isOpen && task) { - const projectId = - projects.find(p => p.name === task.project)?.id || 'none'; - - setFormData({ - title: task.title || '', - description: task.description || '', - project: projectId, - category: task.category || 'none' - }); - - setTimeData({ - startTime: formatTimeForInput(task.startTime), - endTime: task.endTime ? formatTimeForInput(task.endTime) : '' - }); - } - }, [task, projects, isOpen]); - - const parseTimeInput = (timeStr: string, baseDate: Date): Date => { - if (!timeStr || !timeStr.includes(':')) { - return baseDate; - } - - const [hoursStr, minutesStr] = timeStr.split(':'); - const hours = parseInt(hoursStr, 10); - const minutes = parseInt(minutesStr, 10); - - if (isNaN(hours) || isNaN(minutes)) { - return baseDate; - } - - const newDate = new Date(baseDate); - newDate.setHours(hours, minutes, 0, 0); - return newDate; - }; - - const handleSave = () => { - const selectedProject = - formData.project !== 'none' - ? projects.find(p => p.id === formData.project) - : undefined; - const selectedCategory = - formData.category !== 'none' - ? categories.find(c => c.id === formData.category) - : undefined; - - const newStartTime = parseTimeInput(timeData.startTime, task.startTime); - const newEndTime = timeData.endTime - ? parseTimeInput(timeData.endTime, task.startTime) - : undefined; - - const updatedTask: Task = { - ...task, - title: formData.title.trim(), - description: formData.description.trim() || undefined, - project: selectedProject?.name || undefined, - client: selectedProject?.client || undefined, - category: selectedCategory?.id || undefined, - startTime: newStartTime, - endTime: newEndTime, - duration: newEndTime - ? newEndTime.getTime() - newStartTime.getTime() - : task.duration - }; - - onSave(updatedTask); - }; - - return ( - - - - Edit Task - - -
-
- - - setFormData(prev => ({ ...prev, title: e.target.value })) - } - placeholder="Enter task title" - /> -
- -
- - - - Edit - Preview - - -