- src/services/notifications.ts - Added useNotificationSettings hook, respects notifications toggle - app/(tabs)/settings.tsx - Added theme mode picker, fixed notifications toggle, added settings sync buttons
Carry Your Live - Mobile To-Do & Calendar App
A minimalist, offline-first task management app built with Expo, React Native, and WatermelonDB. Features multiple screens with bottom navigation, social features, and a full-featured backend API.
📝 Recent Progress (v1.1.0 - Unreleased)
Added
- Friends feature: Friend requests, accept/decline/remove, user search, full backend routes
- Assignee field on tasks: Schema v7, model, types, forms, sync, task actions
- AssigneeSelector component and assignee display in TaskItem
- QuickAddBar on tasks overview (copied from calendar)
- Backend: Mounted friends route, added assigneeId to task validation and sync
Changed
- Due date prefilled from Calendar "+" button
- 12-hour time formatting across task rows and pickers
- Calendar strip scrolls to selected day when navigating months
- Calendar completion toggle now handles repeating tasks and reminders
- Task color strips now use the task's category color
Fixed
- WheelTimePicker FlatList scroll clamping - all hours (0-23) and minutes (0-59) reachable
- WheelTimePicker selection highlight background connecting selected item with colon separator
- WheelTimePicker number alignment in scroll list
- Tasks without a due date no longer get overdue styling
useOverdueTasksinfinite re-subscription loop (Date.now in effect deps)- Web/Android crashes from decorator transform ordering (babel config)
- FormButtons crash outside FormProvider; Cancel navigation fallback
- Task repeat settings, reminders and series info now sync to backend
Removed
- Task color picker (task color now comes from its category)
- Drag lift popup from TaskItem (scale + shadow on drag)
- Checkbox from task items
- Selection bar overlapping QuickAddBar on tasks overview
Features
Core Functionality
- Offline-First Architecture: Full CRUD operations work offline with WatermelonDB + SQLite
- Auto-Sync: Background synchronization with custom Postgres API when online
- Cross-Platform: iOS, Android, and Web from single codebase
- Desktop Support: Electron wrapper for desktop app distribution
Screens
1. Tasks Screen (To-Do List)
- Header: App logo + "TODO" title with rounded corners
- Category Filter: Horizontal scrollable chips (Work, Personal, School, Shopping, Health, Finance, All)
- Single selection with visual feedback (thicker border, shadow, scale animation)
- Instant task filtering
- QuickAddBar: Quick task creation from list view
- Task List: Virtualized FlatList with sections (Pending/Completed)
- Task title (truncated with ellipsis)
- Three-dot overflow menu (Edit, Delete, Duplicate, Mark Complete, Change Category, Change Priority)
- Priority badges with colors
- Due date/time with overdue/due-today highlighting
- Category color indicator strip
- Assignee display
- Swipe actions (left: delete, right: complete)
- Drag-to-reorder with visual drop indicators
- Long press → Selection mode (multi-delete, multi-complete, change category/priority)
- Floating Action Button: Bottom-right, opens Add Task screen
2. Calendar Screen
- Date Strip: Horizontal scrollable days of current month
- Current day highlighted, scrolls to selected day on month change
- Month Label: Centered (e.g., "JAN 2026")
- QuickAddBar: Quick task creation for selected date
- Task List: Tasks for selected day with category color, title, time, completion status
- Analog Clock Widget: Decorative analog clock display
- Empty State: "No tasks scheduled."
- Day View: Detailed view for selected day (pushes to stack)
3. Stats Screen
- Client-side statistics (
useStatshook) - Task completion metrics
- Category and priority breakdowns
4. Settings Screen
- Account: Login/Register/Sign Out (SyncModal)
- Server URL Override: Custom API endpoint configuration
- Appearance: Accent color picker (dark mode toggle planned)
- Task Defaults: Default category, sort order (Date, Priority, Alphabetically, Creation Date), show completed toggle
- Calendar: Days ahead to display
- Reminders: Global reminder preference (stored, wiring in progress)
- Notifications: Toggle (stored, wiring in progress)
- Legal: Privacy Policy, Terms of Service
- About: Version info
5. Add New Task Screen
- Category Selector: Horizontal row of colored circles (required)
- Task Name: Single-line input, 100 char max (required)
- Subtasks: Unlimited, add/remove individually, nested subtask support
- Date & Time: Date picker, time picker (WheelTimePicker), calendar button
- Priority: Dropdown (None, Low, Medium, High, Critical) with color indicators
- Repeat Profile: Recurring task configuration
- Reminder: Reminder offset selector
- Assignee: Assign task to friend
- Description: Multi-line text area, 1000 char max
- Bottom Actions: Submit (primary) / Cancel (secondary) fixed buttons
6. Task Detail Screen
- Full task view with all fields
- Subtask management (add, complete, reorder, convert to task)
- Edit functionality
- Repeat profile configuration
7. Subtask Detail Screen
- Nested subtask management
- Convert subtask ↔ task
- Re-parent subtasks
8. Sync Modal
- Login/Register forms
- Forgot/Reset password flow
- Server URL configuration
- Sync status display
Task Features
- Nested Subtasks: Unlimited depth, re-parenting, drag reorder
- Repeating Tasks: Repeat profiles, series IDs, this/future/all delete scopes, undo-complete
- Categories/Tags: 6 default categories with colors, custom category editor
- Priorities: None, Low, Medium, High, Critical with color badges
- Due Dates/Times: With all-day option
- Reminders: Per-task reminder offsets
- Assignees: Assign tasks to friends
- Overdue/Due Today: Visual highlighting
Social Features
- Friends System: Send/accept/decline/remove friend requests
- User Search: Find users by username/email
- Task Assignment: Assign tasks to friends (client-side, backend schema ready)
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Expo (React Native) / Expo Router v3 |
| Language | TypeScript (strict mode) |
| Navigation | Expo Router (file-based) |
| Database | WatermelonDB + SQLite (expo-sqlite) |
| State | WatermelonDB reactive queries (no Redux) |
| Forms | React Hook Form + Zod validation |
| UI Components | React Native Paper + Custom components |
| Animations | React Native Reanimated v3 |
| Date/Time | @react-native-community/datetimepicker + date-fns + WheelTimePicker |
| Build | EAS Build / Gradle (local APK) / Electron Builder (desktop) |
| CI/CD | Gitea Actions (.gitea/workflows/build-apk.yml) |
| Backend | Node.js + Express + TypeScript |
| Backend DB | PostgreSQL + Drizzle ORM |
| Auth | JWT (access + refresh tokens) |
| Validation | Zod (shared with frontend) |
Project Structure
carry-your-live/
├── app/
│ ├── _layout.tsx # Root layout + DatabaseProvider + AuthProvider
│ ├── (tabs)/
│ │ ├── _layout.tsx # Bottom tab navigator
│ │ ├── index.tsx # Tasks screen
│ │ ├── calendar.tsx # Calendar screen
│ │ ├── stats.tsx # Stats screen
│ │ └── settings.tsx # Settings screen
│ ├── add-task.tsx # Add Task screen
│ ├── day-view.tsx # Calendar day detail
│ ├── task-detail.tsx # Task view/edit
│ └── subtask-detail.tsx # Subtask view/edit
├── src/
│ ├── components/ # Reusable UI components (30+)
│ │ ├── Header.tsx
│ │ ├── CategoryFilter.tsx
│ │ ├── TaskList.tsx
│ │ ├── TaskItem.tsx
│ │ ├── FloatingActionButton.tsx
│ │ ├── CategorySelector.tsx
│ │ ├── TaskNameInput.tsx
│ │ ├── SubtasksSection.tsx
│ │ ├── DateTimePicker.tsx
│ │ ├── PrioritySelector.tsx
│ │ ├── DescriptionInput.tsx
│ │ ├── FormButtons.tsx
│ │ ├── ListItem.tsx
│ │ ├── AnalogClock.tsx
│ │ ├── AssigneeSelector.tsx
│ │ ├── CategoryEditorModal.tsx
│ │ ├── ColorPickerInput.tsx
│ │ ├── ColorWheel.tsx
│ │ ├── DateTimeInput.tsx
│ │ ├── ForgotPasswordModal.tsx
│ │ ├── FriendsModal.tsx
│ │ ├── LegalModal.tsx
│ │ ├── OptionPickerModal.tsx
│ │ ├── QuickAddBar.tsx
│ │ ├── ReminderSelector.tsx
│ │ ├── RepeatSelector.tsx
│ │ ├── ServerUrlModal.tsx
│ │ ├── SubtaskItem.tsx
│ │ ├── SyncModal.tsx
│ │ ├── SyncStatus.tsx
│ │ ├── TabBarIcon.tsx
│ │ ├── TaskDeleteModal.tsx
│ │ ├── TaskOverflowMenu.tsx
│ │ ├── UpdateNotifier.tsx
│ │ └── WheelTimePicker.tsx
│ ├── constants/
│ │ └── index.ts # Categories, priorities, colors, reminder options
│ ├── database/
│ │ ├── schema.ts # WatermelonDB schema (v21)
│ │ ├── index.ts # Database instance + collections
│ │ ├── sync.ts # Custom API sync adapter (25KB)
│ │ ├── migrations.ts # Schema migrations
│ │ ├── adapter.ts # Platform-specific adapters
│ │ └── tombstones.ts # Deleted record tracking
│ ├── hooks/
│ │ ├── useDatabase.tsx # Database context & providers
│ │ ├── useTasks.tsx # Task queries with reactive state
│ │ ├── useCategories.tsx # Category queries
│ │ ├── useTasksByDate.tsx # Calendar date queries
│ │ ├── useFriends.tsx # Friends state & actions
│ │ ├── useAuth.tsx # Authentication state
│ │ └── useStats.tsx # Statistics computation
│ ├── models/
│ │ ├── Task.ts # Task model (assigneeId, repeat, reminders)
│ │ ├── Category.ts
│ │ ├── Subtask.ts
│ │ ├── RepeatProfile.ts
│ │ ├── Friendship.ts
│ │ └── Tombstone.ts
│ ├── services/
│ │ ├── api.ts # API client with auth
│ │ ├── auth.ts # Auth token management
│ │ ├── friends.ts # Friends API calls
│ │ ├── notifications.ts # Reminder scheduling (expo-notifications)
│ │ ├── repeat.ts # Repeat task generation
│ │ └── taskActions.ts # Task CRUD + sync operations
│ ├── theme/
│ │ ├── index.ts # Theme context, colors, spacing
│ │ └── theme.tsx # Accent color picker, settings sync
│ ├── types/
│ │ └── index.ts # TypeScript interfaces
│ └── utils/
│ ├── date.ts # Date formatting utilities
│ ├── format.ts # General formatting
│ └── validation.ts # Zod schemas
├── backend/ # Backend API (separate package)
│ ├── src/
│ │ ├── index.ts # Express app entry
│ │ ├── db/
│ │ │ ├── schema.ts # Drizzle schema (Postgres)
│ │ │ └── index.ts # DB connection
│ │ ├── routes/
│ │ │ ├── auth.ts # Register, login, me, forgot/reset password
│ │ │ ├── categories.ts # Category CRUD
│ │ │ ├── tasks.ts # Task CRUD + batch
│ │ │ ├── subtasks.ts # Subtask CRUD
│ │ │ ├── sync.ts # Pull/push sync endpoints
│ │ │ ├── users.ts # User profile & settings
│ │ │ ├── friends.ts # Friend requests, search, management
│ │ │ ├── repeatProfiles.ts# Repeat profile CRUD
│ │ │ └── pushNotifications.ts
│ │ ├── middleware/
│ │ │ ├── auth.ts # JWT verification
│ │ │ ├── errorHandler.ts
│ │ │ └── validation.ts # Zod validation middleware
│ │ ├── services/
│ │ └── utils/
│ ├── drizzle.config.ts
│ └── Dockerfile
├── electron/ # Desktop app entry
├── android/ # Native Android project
├── assets/ # Images, fonts
├── patches/ # patch-package fixes
└── plugins/ # Expo config plugins
Data Models
Task
{
id: string;
title: string;
description: string;
categoryId: string;
priority: 'none' | 'low' | 'medium' | 'high' | 'critical';
completed: boolean;
dueDate: number; // timestamp (ms)
dueTime: string; // "HH:MM" (24h stored, 12h displayed)
allDay: boolean;
assigneeId: string | null;
repeatProfileId: string | null;
seriesId: string | null; // For repeating tasks
isTemplate: boolean; // Series template
reminderOffset: string | null; // e.g., "5m", "1h", "1d"
createdAt: Date;
updatedAt: Date;
subtasks: Subtask[];
}
Category
{
id: string;
name: string;
color: '#HEX';
order: number;
createdAt: Date;
updatedAt: Date;
}
Subtask
{
id: string;
taskId: string;
parentSubtaskId: string | null; // For nesting
title: string;
completed: boolean;
order: number;
assigneeId: string | null;
createdAt: Date;
updatedAt: Date;
}
Repeat Profile
{
id: string;
name: string;
frequency: 'daily' | 'weekly' | 'monthly' | 'yearly' | 'custom';
interval: number;
daysOfWeek: number[]; // 0-6 for weekly
dayOfMonth: number | null; // For monthly
monthOfYear: number | null; // For yearly
endDate: number | null; // Timestamp
occurrences: number | null;
createdAt: Date;
updatedAt: Date;
}
Friendship
{
id: string;
userId: string; // Requester
friendId: string; // Recipient
status: 'pending' | 'accepted' | 'declined' | 'blocked';
createdAt: Date;
updatedAt: Date;
}
Default Categories
| Name | Color | Order |
|---|---|---|
| Work | #E53935 (Red) | 0 |
| Personal | #43A047 (Green) | 1 |
| School | #1E88E5 (Blue) | 2 |
| Shopping | #FB8C00 (Orange) | 3 |
| Health | #8E24AA (Purple) | 4 |
| Finance | #FDD835 (Yellow) | 5 |
Priority Colors
| Priority | Color |
|---|---|
| None | #9E9E9E |
| Low | #43A047 |
| Medium | #FB8C00 |
| High | #E53935 |
| Critical | #B71C1C |
Getting Started
Prerequisites
- Node.js 20+
- npm or yarn
- Expo CLI (
npm install -g expo-cli) - iOS Simulator (Mac) / Android Studio for mobile testing
- PostgreSQL 16+ (or Docker)
Installation
cd carry-your-live
npm install
cd ../backend
npm install
Development
Frontend
cd carry-your-live
# Start Expo dev server
npx expo start
# Run on specific platform
npx expo start --ios
npx expo start --android
npx expo start --web
Backend
cd backend
# Start PostgreSQL (Docker)
docker-compose up -d postgres
# Configure environment
cp .env.example .env
# Edit .env with your settings
# Run migrations
npm run db:push
# Start dev server
npm run dev
Server runs at http://localhost:3000 (health check: GET /health)
Desktop (Electron)
cd carry-your-live
# Development with live reload
npm run desktop:dev
# Production build
npm run desktop:dist
Building
EAS Build (cloud)
# Install EAS CLI
npm install -g eas-cli
# Configure project
eas build:configure
# Build for platforms
eas build --platform ios
eas build --platform android
eas build --platform web
Profiles defined in eas.json (development, preview, production; production auto-increments version code).
Gradle (local Android APK)
cd carry-your-live/android
# Debug APK (unsigned)
./gradlew assembleDebug
# Release APK (signed with debug keystore)
./gradlew assembleRelease
Output:
android/app/build/outputs/apk/debug/app-debug.apk
android/app/build/outputs/apk/release/app-release.apk
CI/CD Pipeline (Gitea Actions)
.gitea/workflows/build-apk.yml builds a release APK on every push to main (also manually triggerable via workflow_dispatch):
- Checkout and setup Node 22, JDK 17 (Temurin)
- Android SDK: installs cmdline-tools, licenses, platform-tools,
platforms;android-35,build-tools;35.0.0,ndk;27.1.12297006 - JS deps:
npm ciincarry-your-live/ - Debug keystore: generates
carry-your-live/android/app/debug.keystorewithkeytool - Build:
./gradlew assembleReleaseincarry-your-live/android/ - Upload: APK saved as
carry-your-live-releaseartifact
To run locally exactly as CI does:
keytool -genkeypair -v \
-keystore carry-your-live/android/app/debug.keystore \
-alias androiddebugkey -storepass android -keypass android \
-keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=Android Debug,O=Android,C=US"
cd carry-your-live/android && ./gradlew assembleRelease
Backend API Specification
Server runs at http://localhost:3000/api (configurable in src/services/api.ts and src/database/sync.ts):
Authentication
POST /api/auth/register- Register new userPOST /api/auth/login- Login (returns access + refresh tokens)POST /api/auth/refresh- Refresh access tokenPOST /api/auth/forgot-password- Request password resetPOST /api/auth/reset-password- Reset password with tokenGET /api/auth/me- Get current user
Categories
GET /api/categories- List categoriesPOST /api/categories- Create categoryPATCH /api/categories/:id- Update categoryDELETE /api/categories/:id- Delete category
Tasks
GET /api/tasks- List tasks (with filters: category, completed, due date range, assignee)GET /api/tasks/:id- Get task with subtasksPOST /api/tasks- Create taskPATCH /api/tasks/:id- Update taskDELETE /api/tasks/:id- Delete task (scopes: this/future/all for repeating)POST /api/tasks/batch- Batch operations
Subtasks
GET /api/subtasks/task/:taskId- List subtasks for taskPOST /api/subtasks/task/:taskId- Create subtaskPATCH /api/subtasks/:id- Update subtaskDELETE /api/subtasks/:id- Delete subtask
Repeat Profiles
GET /api/repeat-profiles- List repeat profilesPOST /api/repeat-profiles- Create repeat profilePATCH /api/repeat-profiles/:id- Update repeat profileDELETE /api/repeat-profiles/:id- Delete repeat profile
Users
GET /api/users/me- Get user with settingsPATCH /api/users/me/settings- Update settings (darkMode, defaultCategoryId, sortOrder, etc.)GET /api/users/search- Search users (for friends)
Friends
GET /api/friends- List accepted friendsGET /api/friends/requests- List pending requests (incoming/outgoing)POST /api/friends/request- Send friend requestPATCH /api/friends/request/:id/accept- Accept requestPATCH /api/friends/request/:id/decline- Decline requestDELETE /api/friends/:id- Remove friend
Sync (Offline-first)
GET /api/sync?since=<timestamp>- Pull changes since timestampPOST /api/sync/push- Push local changes
Sync Protocol
The sync endpoint uses a cursor-based approach with tombstones:
-
Pull: Client sends
sincetimestamp, server returns all changes since then{ "categories": [...], "tasks": [...], "subtasks": [...], "repeatProfiles": [...], "friendships": [...], "tombstones": [...], "timestamp": 1234567890 } -
Push: Client sends batched changes with
lastPulledAt{ "changes": { "categories": [...], "tasks": [...], "subtasks": [...], "repeatProfiles": [...], "friendships": [...] }, "tombstones": [...], "lastPulledAt": 1234567890 } -
Conflict Resolution: Last-write-wins based on
updatedAt(customizable insrc/database/sync.ts)
Configuration
Frontend Environment Variables
Create .env file in carry-your-live/:
EXPO_PUBLIC_API_URL=http://your-api-url/api
Backend Environment Variables
Create .env file in backend/:
DATABASE_URL=postgresql://user:pass@localhost:5432/carry_your_live
JWT_SECRET=your-32-char-secret
PORT=3000
NODE_ENV=development
FRONTEND_URL=http://localhost:8081
App Config
Modify app.json for:
- App name, slug, version
- Icon, splash screen
- Permissions (notifications, etc.)
- Orientation, theme
Visual Style Guide
- Background: White (#FFFFFF) / Dark mode (planned)
- Primary Accent: #E53935 (Pink/Red) - customizable
- Typography: Black (#212121) primary, #757575 secondary, #9E9E9E tertiary
- Borders: Thin (#E0E0E0), selected #E53935
- Radius: 16-20px for cards/buttons/inputs
- Spacing: 8px grid system
- Shadows: Minimal (elevation 1-3)
- Animations: 150-250ms, cubic easing
Offline-First Architecture
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Expo App │────▶│ WatermelonDB │────▶│ Backend │
│ (iOS/Android│ │ (SQLite) │ │ (Postgres) │
│ Web) │◀───▶│ + Sync │◀───▶│ │
└─────────────┘ └──────────────┘ └─────────────┘
- All writes go to local SQLite first
- Reactive queries update UI instantly
- Background sync pushes/pulls changes
- Conflict resolution: last-write-wins (customizable)
- Tombstones track deletions for sync
Current Implementation Status
Based on code audit (August 2026):
| Feature | Status | Notes |
|---|---|---|
| Core CRUD (tasks, subtasks, categories) | ✅ Complete | |
| Nested subtasks | ✅ Complete | Unlimited depth |
| Repeating tasks | ✅ Complete | Profiles, series, this/future/all delete |
| Categories & priorities | ✅ Complete | |
| Calendar with date strip | ✅ Complete | Month nav, day detail |
| Stats screen | ✅ Complete | Client-side only |
| Settings screen | ✅ Complete | Most settings wired |
| Friends system | ✅ Complete | Backend + UI |
| Task assignees | ✅ Complete | Schema v7, UI, sync |
| QuickAddBar | ✅ Complete | Tasks + Calendar |
| Offline sync (WatermelonDB) | ✅ Complete | Schema v21, tombstones |
| Backend API | ✅ Complete | Express + Drizzle + Postgres |
| Auth (JWT) | ✅ Complete | Register, login, refresh, forgot/reset |
| Dark mode / theme toggle | ❌ Missing | Backend column exists, UI not implemented |
| Notifications toggle | 🟡 Partial | Stored but not respected |
| Global reminder preference | 🟡 Partial | Stored but not applied |
| Forgot/reset password UI | 🟡 Partial | Backend done, no client UI |
| Pull-to-refresh → sync | 🟡 Partial | Reloads local only |
| Client settings sync | 🟡 Partial | AsyncStorage only, not synced to backend |
| Reminder enum mismatch | 🟡 Partial | Client uses '5m'/'1h', spec uses minutes |
Roadmap Highlights
v1.1.0 — Friends & Assignees (Current Unreleased)
- Ship existing Unreleased changes
- Fix notifications toggle, wire up
rescheduleAllReminders() - Apply global reminder preference
- Make pull-to-refresh trigger real sync
- Add forgot/reset password UI
- Unify reminder offset enums
v1.2.0 — Theming & Findability
- Light mode + follow-system-theme
- Sync all settings to
PATCH /users/me/settings - Task search (filter + global)
- Friend request badges/indicators
v1.3.0 — Stats & Insights
- Persisted stats snapshots
- Productivity trends, streaks
- Export stats (CSV/image)
v1.4.0 — Quick Capture & Calendar UX
- Natural language quick-add
- Drag-and-drop rescheduling in calendar
- Month grid view
- "Today at a glance" dashboard
v1.5.0 — Power Tools & Personalization
- Task templates, soft-archive, attachments
- Markdown support, deep links
- Biometric lock, haptics, reduced motion
- Android home-screen widget
v2.0.0 — Collaboration
- Real task assignment delivery
- Shared lists with permissions
- Server push notifications (FCM/APNs)
- Comments/activity feed
Contributing
- Follow existing code conventions
- Use TypeScript strict mode
- Write components with React Hook Form + Zod
- Use WatermelonDB decorators for models
- Test offline scenarios
- Run
npm run lintandnpm run typecheckbefore committing
License
MIT