Introduction
What does it look like to build a production-ready full-stack web application in 2+ sprints, with AI as a core part of the development process? That was the challenge we took on with DailyMood, a mood tracking app that lets users log how they feel each day, browse their history on a calendar, and understand their emotional patterns through a trends dashboard.
In this post we walk through the technical decisions we made, the architecture we landed on, the challenges we hit along the way, and most importantly, how we used two distinct AI modalities to accelerate every phase of the project without sacrificing code quality.
The code is available on GitHub:
https://github.com/arinaa77/DailyMood-An-AI-Powered-Mood-Tracker
The live application is available at:
https://daily-mood-an-ai-powered-mood-track.vercel.app
The Problem We Were Solving
Most journaling and mood tracking apps ask for too much. They want paragraphs, categories, tags, and ratings before you have even finished your morning coffee. Our core UX constraint was simple: a full entry in under 30 seconds. Pick a mood. Add an optional note. Done.
The challenge was building something that felt lightweight on the surface while being technically solid underneath: real authentication, a relational database with security policies, real-time sync across browser tabs, a responsive UI that works on both desktop and mobile, and a CI/CD pipeline that enforces quality on every push.
Tech Stack Decisions
Before writing a single line of code, we spent time in Claude Web defining the tech stack.
Next.js 15 with App Router
We chose Next.js because it handles both frontend and backend in a single project. API route handlers replace the need for a separate Express server, server components give us SSR without configuration, and the App Router's route groups let us cleanly separate authenticated and unauthenticated screens.
Supabase
Supabase provides PostgreSQL with Row Level Security, JWT-based authentication, and realtime subscriptions in one managed platform. Instead of wiring together a separate database, auth service, and WebSocket server, Supabase collapsed everything into a single SDK and dashboard.
Tailwind CSS 4
Tailwind's utility-first approach allowed us to build consistent styling without maintaining custom CSS classes. We used Plus Jakarta Sans via next/font/google for a modern typographic feel.
Recharts
Recharts is a React-native charting library that integrates smoothly with React components. It allowed us to build charts without dealing with the complexity of D3 or managing canvas rendering manually.
Vitest + Playwright
Vitest with React Testing Library was used for unit and integration tests, while Playwright handled end-to-end tests. Playwright tests used HTTP-level mocking of Supabase calls. This approach allowed us to achieve 97% coverage without requiring a real database during testing.
Architecture
The application includes three authenticated screens that share a common layout.
Log Page
The MoodPicker component handles mood selection and note input. A sidebar shows today's status, the current week's mood strip, the user's streak, and the total number of entries. All data flows through a custom hook called useMoodEntries, which manages CRUD operations and maintains a Supabase realtime subscription.
Calendar Page
The calendar page displays a monthly grid built with date-fns. Clicking a date expands the full entry. Editing and deleting entries both open confirmation modals to avoid accidental destructive actions.
Insights Page
The insights page includes three stat cards (average mood, top mood, and entry count), a Recharts bar chart with selectable time ranges (7 days, 30 days, 90 days), and a Personal Records card showing the longest streak, best month, favorite mood, and total entries. All records are calculated client-side from existing data.
One architectural rule we enforced strictly was that data fetching lives in hooks, never directly in components. Components receive data through props or hooks. This keeps components pure and easily testable while isolating the data layer for mocking during tests.
Database Design
The mood_entries table was intentionally designed to remain simple.
create table mood_entries (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) on delete cascade not null,
mood_score integer not null check (mood_score between 1 and 5),
note text default '' not null,
created_at timestamptz default now() not null
);
The constraint check (mood_score between 1 and 5) enforces the mood scale directly at the database level. The foreign key with on delete cascade ensures that deleting a user account automatically removes all associated entries.
Row Level Security (RLS) policies ensure users can only read or write their own rows, even if a bug in the application layer omits the user filter. As an additional safety layer, every query explicitly includes .eq('user_id', user.id).
Real-Time Updates
Real-time synchronization significantly improves the user experience. When an entry is saved on one device, the calendar updates on other open sessions almost immediately.
This is implemented using a Supabase realtime subscription inside the useMoodEntries hook.
const channel = supabase
.channel('mood_entries_changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'mood_entries' },
() => fetchEntries(),
)
.subscribe();
The channel listens for INSERT, UPDATE, and DELETE events on the mood_entries table and triggers a refetch whenever data changes. The subscription is cleaned up when the component unmounts to prevent memory leaks.
Testing Strategy
We set 80% coverage as the minimum requirement enforced by CI and ultimately reached 99%.
The testing strategy followed a three-layer test pyramid.
Unit Tests
We wrote 119 unit tests using Vitest and React Testing Library. Tests simulate user actions such as clicking, typing, and submitting forms. The userEvent API was used instead of fireEvent to better simulate real user behavior. Recharts was mocked entirely because jsdom does not support SVG rendering.
Integration Tests
Integration tests focus on page-level behavior. The useMoodEntries hook was mocked to return controlled datasets. These tests verified logic such as whether the sidebar correctly displays "Entry logged today" or "Not logged yet," and whether streak calculations in MilestoneCards behave correctly.
End-to-End Tests
Playwright runs full browser tests against the actual Next.js server. Supabase HTTP calls are intercepted with page.route() and replaced with mock responses. These tests verify navigation between pages, the full entry logging flow, and mobile responsiveness using a Pixel 5 viewport.
One debugging challenge occurred when Vitest accidentally detected Playwright .spec.ts files and attempted to run them, causing a crash. The solution was adding an exclude rule in vite.config.js.
CI/CD Pipeline
Every push to the main branch triggers a four-stage GitHub Actions pipeline.
Lint
ESLint with Next.js rules runs first and fails immediately on errors.
Test and Coverage
Vitest runs all tests and enforces coverage thresholds. Reports are uploaded to Codecov.
Security Scan
npm audit with the high severity threshold detects dependency vulnerabilities.
Build
A production Next.js build verifies TypeScript compilation and page generation.
Stages three and four only run if linting and tests pass.
Vercel is connected to the GitHub repository. Every push to main triggers a production deployment, while each pull request generates a preview deployment URL for UI review.
How We Used AI: Two Modalities
The most interesting part of this project was intentionally using two different AI tools for different phases of development.
Claude Web for Planning
Claude Web was used before any code was written. It helped draft the product requirements document (PRD), generate wireframes, design the database schema, and define the technology stack. The conversational workflow allowed iterative discussion of tradeoffs and requirements.
We also created a project memory folder containing the PRD and wireframes so the implementation phase had a clear specification to follow.
Antigravity for Implementation
Antigravity acts as an IDE-integrated AI assistant with access to the file system, terminal, and codebase context. It uses cursor position, open tabs, the import graph, and code embeddings to understand the project structure automatically.
We used Antigravity's planning mode to break features into parallel tasks, its skills system to enforce Scrum practices and test coverage requirements, and its self-review capability to run a pre-commit audit that caught lint errors before CI.
A key technique was maintaining an agent.md file as a lock file containing a chronological log of architectural decisions, fixes, and changes. When the AI session reached its context window limit, agent.md allowed a new session to reconstruct the project state instantly.
Challenges and Solutions
Next.js Breaking Change
A framework update renamed middleware.ts to proxy.ts with a different export signature. Since this change happened after the AI's training data cutoff, the solution was providing the real error message so the AI could reason using the latest information.
SSR Hydration Mismatch
A useMemo function containing Math.random() produced different values between server and client rendering. React detected the mismatch during hydration. The fix was replacing the random value with a constant placeholder string.
Coverage Scope
Including Next.js page files in unit test coverage lowered the metrics because page components rely on server-only APIs incompatible with jsdom. The solution was scoping coverage to src/components, src/lib, and src/app/(auth). Page-level behavior was covered through Playwright tests instead.
What We Would Do Differently
If we restarted the project, we would establish the agent.md lock file from the beginning and require updates after each major change. In practice, we introduced it midway through development and had to reconstruct some early decisions.
We would also configure the CI pipeline earlier. A pipeline that enforces tests and security on every push provides a safety net that allows faster iteration on features.
Conclusion
DailyMood became more than just a mood tracker: it became an experiment in deliberate AI-assisted software development.
Using Claude Web for planning and Antigravity for implementation allowed each stage of development to use the most appropriate AI tool. The final result was a production-deployed full-stack application with 119 tests, 99% coverage, a four-stage CI pipeline, realtime synchronization, and a polished UI across desktop and mobile.
Top comments (0)