Events created with specific occurrence counts (like "repeat 5 times") were
repeating forever instead of stopping after the specified number.
Root cause: Frontend form collected recurrence_count and recurrence_until
values correctly, but these weren't being passed through the event creation
pipeline to the backend, which was hardcoding None values.
Fix implemented across entire creation flow:
1. **Enhanced Parameter Conversion**:
- Added recurrence_count and recurrence_until to to_create_event_params() tuple
- Properly extracts values from form: recurrence_count, recurrence_until.map()
2. **Updated Backend Method Signature**:
- Added recurrence_count: Option<u32> and recurrence_until: Option<String>
- to create_event() method parameters
3. **Fixed Backend Implementation**:
- Replace hardcoded None values with actual form parameters
- "recurrence_end_date": recurrence_until, "recurrence_count": recurrence_count
4. **Updated Call Sites**:
- Modified app.rs to pass params.18 (recurrence_count) and params.19 (recurrence_until)
- Proper parameter indexing after tuple expansion
Result: Complete recurrence control now works correctly:
- ✅ Events with COUNT=5 stop after exactly 5 occurrences
- ✅ Events with UNTIL date stop on specified date
- ✅ Events with "repeat forever" continue indefinitely
- ✅ Proper iCalendar RRULE generation with COUNT/UNTIL parameters
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed multiple issues with recurring event editing via modal that were causing
events to be created instead of updated, and API parameter mismatches.
Key fixes:
1. **Restore Update Flow**:
- Added original_uid tracking to EventCreationData to distinguish create vs update
- Modal now routes to update endpoints when editing existing events instead of always creating new ones
- Implemented dual-path logic in on_event_create callback to handle both operations
2. **Fix "This and Future" Updates**:
- Added occurrence_date field to EventCreationData for recurring event context
- Backend now receives required occurrence_date parameter for this_and_future scope
- Populated occurrence_date from event start date in modal conversion
3. **Fix Update Scope Parameters**:
- Corrected scope parameter mapping to match backend API expectations:
* EditAll: "entire_series" → "all_in_series"
* EditFuture: "this_and_future" (correct)
* EditThis: "this_event_only" → "this_only"
4. **Enhanced Backend Integration**:
- Proper routing between update_event() and update_series() based on event type
- Correct parameter handling for both single and recurring event updates
- Added missing parameters (exception_dates, update_action, until_date)
Result: All recurring event edit operations now work correctly:
- ✅ "Edit all events in series" updates existing series instead of creating new
- ✅ "Edit this and future events" properly handles occurrence dates
- ✅ "Edit this event only" works for single instance modifications
- ✅ No more duplicate events created during editing
- ✅ Proper CalDAV server synchronization maintained
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
When editing existing events, the modal was showing empty/default values
instead of the current event data, making editing very inconvenient.
Root cause: TODO comment in modal initialization was never implemented -
VEvent to EventCreationData conversion was missing.
Solution: Implemented comprehensive vevent_to_creation_data() function that maps:
- Basic info: title, description, location, all-day status
- Timing: start/end dates/times with proper UTC→local timezone conversion
- Classification: event status (Confirmed/Tentative/Cancelled) and class
- People: organizer and attendees (comma-separated)
- Categories: event categories (comma-separated)
- Calendar selection: finds correct calendar or falls back gracefully
- Recurrence: detects recurring events (with TODO for advanced RRULE parsing)
- Priority: preserves event priority if set
Features:
- Proper timezone handling for display times
- Fallback logic for missing end times (1 hour default)
- Smart calendar matching with graceful fallbacks
- Complete enum type mapping between VEvent and EventCreationData
Result: Edit modal now pre-populates with all existing event data,
making editing user-friendly and preserving all event properties.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
All-day events spanning multiple days were only showing on their start date.
For example, a "Vacation (Dec 20-25)" event would only appear on Dec 20th.
Root cause: Logic only checked events stored in each day's HashMap entry,
missing events that span into other days.
Solution:
- Modified all-day event collection to search all events across all days
- Added event_spans_date() helper function to check if event spans given date
- Properly handles iCalendar dtend convention (day after event ends)
- Added deduplication to prevent duplicate events from multiple day buckets
- Removed unused day_events variable
Result: Multi-day all-day events now correctly appear on every day they span,
while single-day events continue to work as before.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Timed events were incorrectly displaying at half-width when all-day events
existed on the same day, even though all-day events display separately at
the top of the calendar and don't visually overlap with timed events.
Root cause: The overlap calculation logic was including all-day events when
determining width splits for timed events.
Solution:
- Modified calculate_event_layout() to exclude all-day events from filtering
- Updated events_overlap() to return false if either event is all-day
- All-day events now don't participate in timed event width calculations
Result: Timed events display at full width unless they actually overlap
with other timed events, while all-day events continue to display correctly
in their separate section.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Events dragged to 4am-7am were appearing at 8am-11am due to double
timezone conversion. The issue was:
1. Frontend converted local time to UTC before sending to backend
2. Backend (after previous fix) converted "local time" (actually UTC) to UTC again
3. Result: double conversion causing 4+ hour shift in wrong direction
Solution: Remove frontend UTC conversion in drag-and-drop callback.
Let backend handle the local-to-UTC conversion consistently.
- Remove .and_local_timezone(chrono::Local).unwrap().to_utc() conversion
- Send NaiveDateTime directly as local time strings to backend
- Backend parse_event_datetime() now properly handles local-to-UTC conversion
Now drag-and-drop works correctly: drag to 4am shows 4am.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Events were appearing 4 hours earlier than selected time due to incorrect
timezone handling in backend. The issue was treating frontend local time
as if it was already in UTC.
- Fix parse_event_datetime() in events.rs to properly convert local time to UTC
- Fix all datetime conversions in series.rs to use Local timezone conversion
- Replace Utc.from_utc_datetime() with proper Local.from_local_datetime()
- Add timezone conversion using with_timezone(&Utc) for accurate UTC storage
Now when user selects 5:00 AM, it correctly stores as UTC equivalent
and displays back at 5:00 AM local time.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add database migration for last_used_calendar field in user preferences
- Update backend models and handlers to support last_used_calendar persistence
- Modify frontend preferences service with update_last_used_calendar() method
- Implement automatic saving of selected calendar on event creation
- Add localStorage fallback for offline usage and immediate UI response
- Update create event modal to default to last used calendar for new events
- Clean up unused imports from event form components
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove the original create_event_modal.rs and rename create_event_modal_v2.rs
to complete the modal migration started earlier. This eliminates duplicate code
and consolidates to a single, clean event modal implementation.
Changes:
- Remove original create_event_modal.rs (2,300+ lines)
- Rename create_event_modal_v2.rs → create_event_modal.rs
- Update component/function names: CreateEventModalV2 → CreateEventModal
- Fix all imports in app.rs and calendar.rs
- Add missing to_create_event_params() method to EventCreationData
- Resolve EditAction type conflicts between modules
- Clean up duplicate types and unused imports
- Maintain backwards compatibility with EventCreationData export
Result: -2440 lines, +160 lines - massive code cleanup with zero functionality loss.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add --calendar-border-light CSS variables to all 8 color themes for proper
15-minute grid line styling. Previously used hard-coded fallback (#f8f8f8)
which was too bright for dark mode and inconsistent with theme colors.
- Dark mode: Use subtle #2a2a2a instead of bright #f8f8f8
- All themes: Theme-appropriate very light border colors
- Better visual integration with each color scheme
- Consistent dotted 15-minute grid lines across all themes
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Hide time display for events with duration <= 30px (single time slots)
to maximize space for event titles in compact event boxes.
- Single-slot events show title only for better readability
- Applies to both 15-minute and 30-minute time increment modes
- Consistent behavior across static events and drag previews
- Improves UX for short duration events where time display crowds the title
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Scale time grid height dynamically based on time increment (1530px/2970px)
- Add quarter-mode CSS classes for 15-minute blocks (30px each, same as 30-min blocks)
- Update pixel-to-time conversion functions with 2px:1min scaling in 15-min mode
- Generate correct number of time slots (4 per hour in 15-min mode)
- Remove unnecessary final boundary time label and related CSS
- Fix CSS grid layout by removing malformed CSS syntax
- All time-related containers scale properly between 30-minute and 15-minute modes
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Extend recurring event expansion date range from 30 days to 100 years in the past.
This ensures yearly recurring events created many years ago (birthdays, anniversaries,
historical dates) properly generate their current year occurrences.
The backend correctly includes old recurring events that could have occurrences in
the requested month, but the frontend was only expanding occurrences within a
30-day historical window, missing events from previous years.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Split massive 27K line modal into focused components
- Created event_form module with 6 tab components:
* BasicDetailsTab - main event info with recurrence options properly positioned
* AdvancedTab - status, privacy, priority
* PeopleTab - organizer and attendees
* CategoriesTab - event categories
* LocationTab - location information
* RemindersTab - reminder settings
- Added shared types and data structures
- Created new CreateEventModalV2 using modular architecture
- Recurrence options now positioned directly after repeat/reminder pickers
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Configure Caddy to proxy /api requests to backend service
- Add favicon_big.png for various icon size needs
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add favicon.ico as site favicon using Trunk asset pipeline
- Remove calendar.db from git tracking (already in .gitignore)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Set all_day flag properly when creating VEvent in series handler
- Improve all-day event detection using VALUE=DATE parameter
- Add RFC-5545 compliance for exclusive end dates (backend adds 1 day)
- Fix end date display in event modal (frontend subtracts 1 day for display)
- Fix recurring all-day event expansion to maintain proper end date pattern
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added intelligent viewport boundary detection that repositions context
menus when they would appear outside the screen:
- Detects right/bottom edge overflow and repositions menus accordingly
- Uses accurate size estimates based on actual menu content
- Event menus: 280×200px (recurring) / 180×100px (non-recurring)
- Calendar/generic menus: 180×60px for single items
- Maintains 5px minimum margins from screen edges
- Graceful fallback to original positioning if viewport detection fails
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
All-day events now have the same right-click context menu functionality
as regular timed events, allowing users to edit, delete, and perform
other actions on all-day events.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previously filtered events by start date only, which excluded recurring
events that started in previous months/years but have instances in the
current month.
New logic:
- Non-recurring events: filter by exact month match (unchanged)
- Recurring events: include if they could have instances in requested month
- Check event start date is before/during month
- Parse RRULE UNTIL date to exclude expired recurring events
- Let frontend handle proper RRULE expansion
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add fallback registry to prevent invalid tag format
- Make Docker login conditional on secrets being present
- Make push conditional on registry being configured
- Rename Docker image from calendar to runway
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented clustering algorithm in calculate_event_layout that:
- Only creates column splits for events that actually overlap
- Non-overlapping events maintain full width display
- Uses greedy column assignment for overlapping groups
- Preserves proper column indices for each event
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Backend fixes:
- Fix all-day event creation validation error
- Allow same start/end date for all-day events (single-day events)
- Maintain strict validation for timed events (end must be after start)
Frontend improvements:
- Move all-day events from time grid to day headers
- Add dedicated all-day events container that stacks vertically
- Filter all-day events out of main time-based events area
- Add proper CSS styling for all-day event display and interaction
- Maintain event click handling and color themes
All-day events now appear in the correct location at the top of each
day column and properly stack when multiple events exist.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update project name in Cargo.toml from calendar-app to runway
- Change HTML title and sidebar header to 'Runway'
- Complete README rewrite with new branding and philosophy
- Add 'The Name' section explaining runway metaphor as passive infrastructure
- Update Dockerfile build references to use new binary name
- Maintain all technical documentation with new branding context
The name 'Runway' embodies passive infrastructure that enables coordination
without getting in the way - like airport runways that provide essential
structure for planes but stay invisible during flight.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix drag-to-create being blocked by existing events
- Add creating-event CSS class that disables pointer events on existing events
- Fix single clicks creating temporary event boxes
- Add mouse button state check to prevent post-mouseup movement being treated as drag
- Ensure temp event boxes only appear during actual drag operations (has_moved=true)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add overlap detection algorithm to identify overlapping events
- Implement layout calculation to arrange events in columns
- Update event positioning to use dynamic left/width instead of fixed right
- Events now render side-by-side when they overlap in time
- Maintains proper spacing and margins for all event arrangements
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unnecessary boundary slot from week view time grid
- Adjust container heights to display full 11 PM - midnight time slot
- Fix timezone issue preventing events at 8 PM or later from rendering
- Update date matching logic to handle UTC-4 timezone offset correctly
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit adds a complete style system alongside the existing theme system, allowing users to switch between different UI styles while maintaining theme color variations.
**Core Features:**
- Style enum (Default, Google Calendar) separate from Theme enum
- Hot-swappable stylesheets with dynamic loading
- Style preference persistence (localStorage + database)
- Style picker UI in sidebar below theme picker
**Frontend Implementation:**
- Add Style enum to sidebar.rs with value/display methods
- Implement dynamic stylesheet loading in app.rs
- Add style picker dropdown with proper styling
- Handle style state management and persistence
- Add web-sys features for HtmlLinkElement support
**Backend Integration:**
- Add calendar_style column to user_preferences table
- Update all database operations (insert/update/select)
- Extend API models for style preference
- Add migration for existing users
**Google Calendar Style:**
- Clean Material Design-inspired interface
- White sidebar with proper contrast
- Enhanced calendar grid with subtle shadows
- Improved event styling with hover effects
- Google Sans typography throughout
- Professional color scheme and spacing
**Technical Details:**
- Trunk asset management for stylesheet copying
- High CSS specificity to override theme styles
- Modular CSS architecture for easy extensibility
- Comprehensive text contrast fixes
- Enhanced calendar cells and navigation
Users can now choose between the original gradient design (Default) and a clean Google Calendar-inspired interface (Google Calendar), with full preference persistence across sessions.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added comprehensive Docker Compose deployment section as recommended method
- Documented automatic database migrations and persistent storage
- Updated architecture section to mention SQLite auth system
- Added new User Experience section highlighting session management
- Reorganized development setup with local database migration instructions
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create /db directory in container
- Ensure database directory exists and has proper permissions in startup script
- Remove problematic conditional COPY command that was causing build failures
This fixes the 'unable to open database file' error by ensuring the
database directory exists before migrations run.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Install sqlx-cli in backend builder stage
- Copy migrations and sqlx binary to runtime image
- Run database migrations automatically on container startup
- Add error handling to prevent startup failure if migrations already applied
This ensures the database schema is always up to date when deploying
with Docker, eliminating manual migration steps.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remember checkboxes now default to checked for better user experience
- Reduced visual prominence with smaller size, lighter colors, and lower opacity
- Users get convenience by default while still being able to opt out
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added SQLite database for session management and user preferences storage,
allowing users to have consistent settings across different sessions and devices.
Backend changes:
- Added SQLite database with users, sessions, and preferences tables
- Implemented session-based authentication alongside JWT tokens
- Created preference storage/retrieval API endpoints
- Database migrations for schema setup
- Session validation and cleanup functionality
Frontend changes:
- Added "Remember server" and "Remember username" checkboxes to login
- Created preferences service for syncing settings with backend
- Updated auth flow to handle session tokens and preferences
- Store remembered values in LocalStorage (not database) for convenience
Key features:
- User preferences persist across sessions and devices
- CalDAV passwords never stored, only passed through
- Sessions expire after 24 hours
- Remember checkboxes only affect local browser storage
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Moved event fetching logic from CalendarView to Calendar component to properly
use the visible date range instead of hardcoded current month. The Calendar
component already tracks the current visible date through navigation, so events
now load correctly for August and other months when navigating.
Changes:
- Calendar component now manages its own events state and fetching
- Event fetching responds to current_date changes from navigation
- CalendarView simplified to just render Calendar component
- Fixed cargo fmt/clippy formatting across codebase
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Removed Obsolete Environment Variables:
- `CALDAV_SERVER_URL` - provided by user login
- `CALDAV_USERNAME` - provided by user login
- `CALDAV_PASSWORD` - provided by user login
- `CALDAV_TASKS_PATH` - not used in any features
## Kept with Intelligent Discovery:
- `CALDAV_CALENDAR_PATH` - optional override, defaults to smart discovery
## Changes:
### Backend
- Remove `CalDAVConfig::from_env()` method (not used in main app)
- Add `CalDAVConfig::new()` constructor with credentials
- Remove `tasks_path` field from CalDAVConfig
- Update auth service to use new constructor
- Update tests to use hardcoded test values instead of env vars
- Update debug tools to use test credentials
### Frontend
- Remove unused `config.rs` file entirely (frontend uses backend API)
## Current Authentication Flow:
1. User provides CalDAV credentials via login API
2. Backend creates CalDAVConfig dynamically from login request
3. Backend tests authentication via calendar discovery
4. Optional `CALDAV_CALENDAR_PATH` env var can override discovery
5. No environment variables required for normal operation
This simplifies deployment - users only need to provide CalDAV
credentials through the web interface, no server-side configuration required.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>