When a remote DELETE is applied before LWW resolution and the local UPDATE
wins (newer timestamp), extract the entity from the DELETE operation payload
to recreate it, preventing data loss from the race condition.
- Created calendar.svg icon for ICAL issue provider
- Registered calendar icon in global theme service
- Updated ISSUE_PROVIDER_ICON_MAP to use 'calendar' instead of 'calendar_month'
- Fixes error: "Unable to find icon with the name :calendar_month"
Prevents the task close button from appearing on mobile screens (≤600px) where task details are shown in the bottom panel, improving UX by avoiding redundant UI elements.
- Fix snackbar selector in supersync edge cases test
- Add polling for plugin navigation stability
- Show error snackbar and status icon on decryption failure
- All 5 failing tests now passing consistently
Resolves issue #6095 where Task View Customizer settings (sort, group, filter)
were being reset on app restart or day change.
Changes:
- Add localStorage keys for sort/group/filter settings
- Initialize signals from localStorage with default fallbacks
- Add effects to auto-persist signal changes to localStorage
- Add 7 comprehensive unit tests for persistence behavior
Settings now persist across app restarts, work context changes, and day boundaries.
Invalid localStorage data gracefully falls back to defaults.
Use Capacitor's native WebView resize mode on iOS instead of CSS-based
workarounds. When keyboard appears, the WebView itself shrinks so 100vh
automatically fits above the keyboard.
- Configure iOS to use `resize: 'native'` (Android keeps `resize: 'body'`)
- Add scrollIntoViewIfNeeded() to scroll focused inputs into view
- Add proper cleanup for keyboard event listeners
- Improve flexbox shrinking in fullscreen markdown dialog
On iOS, dismissing the native share sheet by tapping the background
would cause it to reopen immediately. Two issues were fixed:
1. The Capacitor Share plugin on iOS throws {errorMessage: "Share canceled"}
but the code only checked for error.name === 'AbortError'. This caused
the cancellation to not be detected, falling through to try the Web
Share API as a fallback, opening a second share dialog.
2. Moved the iOS share guard from component level to ShareService. The
component-level guard didn't work because WorkContextMenuComponent is
inside ng-template matMenuContent, so it gets destroyed when the menu
closes, losing the guard state.
- Replace deprecated `selector:` properties with proper Electron `role:` in macOS menu
- Add standard macOS menu items (hide, hideOthers, unhide)
- Ensure before-close handlers always call setDone() to prevent app hanging
- Change sync error dialog from confirm() to alert() since result was ignored
Implement lazy loading for material-icons.const.ts (69.5KB, 3800+ icons) to reduce initial bundle size by ~68KB.
Changes:
- Create MaterialIconsLoaderService with promise caching to prevent concurrent loads
- Update DialogCreateTagComponent to use lazy loader service
- Update IconInputComponent to use lazy loader service
- Add comprehensive unit tests for MaterialIconsLoaderService
- Convert icon input methods to async for lazy loading support
Expected impact: Main bundle reduced by ~69KB, icons loaded on-demand when user focuses icon input fields.
- Replace hardcoded 7-day navigation with responsive day counts
- Desktop (≥1200px): 7 days (full week)
- Tablet (768-1199px): 5 days
- Mobile (480-767px): 3 days
- Small mobile (<480px): 2 days
- Navigation now moves by the number of days currently shown
- Remove horizontal scroll (no longer needed with responsive day count)
This fixes the mobile navigation bug where users saw 3 days but
navigation skipped by 7 days, causing 4 days to be hidden between
each navigation action.
- Move horizontal scroll control to parent .scroll-wrapper element
- Both vertical and horizontal scrollbars now on same container
- Pass isHorizontalScrollMode as input to schedule-week component
- Remove duplicate scroll wrapper from schedule-week
- Maintain responsive column widths based on scroll mode
- Fixes scrollbar positioning and coordination issues
This ensures both scrollbars are managed by the same element, providing
better UX and preventing scrollbar positioning conflicts.
- Wrap content in .horizontal-scroll-wrapper to control scroll positioning
- Move overflow from :host to wrapper to keep scrollbar at viewport level
- Use app's standard scrollbar styling (4px width, themed colors)
- Scrollbar now stays visible regardless of vertical scroll position
- Increase height to 8px for better horizontal scroll visibility
- Change overflow-x from auto to scroll to always show scrollbar
- Fix overflow-y: visible issue that prevented proper scrollbar display
- Increase scrollbar height to 14px for better visibility
- Use scrollbar-width: thin for Firefox (always visible)
- Improve scrollbar styling with better contrast
- Week view now always shows all 7 days with navigation skipping full weeks
- Add horizontal scroll when viewport < 1900px to show hidden days
- Implement responsive column widths (180px desktop, 150px tablet, 120px mobile)
- Columns scale up with minmax() when extra space available
- Add visible themed scrollbar for better UX
- Simplify navigation logic: always skip 7 days forward/backward
- Simplify day generation: sequential days from reference date
- Update tests to match new 7-day navigation behavior
Changes the mobile navigation drawer to slide in from the right side
instead of the left, improving spatial consistency with the nav button
(positioned far right in bottom nav) and enhancing right-handed UX.
Implementation:
- Add position-right class to drawer when in mobile mode
- Implement dual animation triggers (mobileNav for desktop/left,
mobileNavRight for mobile/right)
- Fix box-shadow direction for right-positioned drawer
- Swap border-right/left based on position
- Desktop behavior unchanged (drawer remains on left)
The deleteTaskRepeatCfg action does not have a taskIdsToUnlink property.
Removed this property from test expectations to match the actual action
definition.
This was causing TypeScript compilation errors in the test file.
Created parseDbDateStr utility to parse 'YYYY-MM-DD' strings as local
dates instead of UTC. Using new Date('2026-01-12') parses as UTC
midnight, which becomes the previous day when converted to timezones
like Europe/Berlin (UTC+1) or America/Los_Angeles (UTC-8).
Fixed in:
- ScheduleService.getDayClass
- ScheduleMonthComponent.referenceMonth
- ScheduleWeekComponent._formatDateLabel
- Test expectations in multiple spec files
All tests now pass in both Europe/Berlin and America/Los_Angeles timezones.
- Remove outdated taskIdsToUnlink field from ArchiveOperationHandler test mocks
- Add cross-client integration test verifying delete operations don't include task IDs
- Ensures deterministic cleanup based on local state rather than synced task lists
SuperSync accessToken was being overwritten with empty string due to
Formly's resetOnHide: true behavior. When form re-renders or user
navigates, the accessToken field resets to empty, and the merge logic
was allowing this empty value to overwrite saved credentials.
Solution: Add defensive merge logic to filter out empty/undefined/null
values from form before merging with saved config. This prevents form
state issues from clearing credentials while still allowing updates
when users provide new non-empty values.
Also fixes undefined stateName variable in is-related-model-data-valid.ts
that was preventing tests from running.
- Add filtering of empty values in _updatePrivateConfig()
- Add comprehensive test coverage:
* SuperSync token preservation (resetOnHide scenario)
* SuperSync token updates with new values
* WebDAV password preservation
* LocalFile path preservation
* Boolean false value preservation (not filtered as empty)
* Multiple empty fields scenario
* Mixed empty and non-empty fields
- Protect all sync providers from similar form state issues
Test Coverage: 24 tests (up from 19), all passing
Confidence: 95% - Root cause clearly identified and addressed
Change meta-reducer to scan local state instead of using synced payload.
This prevents cross-client divergence when deleting repeat configs.
- Meta-reducer now scans NgRx state for all tasks with matching repeatCfgId
- Remove taskIdsToUnlink from action payload (deterministic cleanup)
- Simplify service method (no longer needs to query tasks)
- Enhanced validation error logging with task ID and state location
- Implement full test suite for ScheduleComponent covering:
- Navigation state management (_selectedDate signal)
- Week/month navigation (goToNextPeriod, goToPreviousPeriod, goToToday)
- Today detection (isViewingToday computed)
- Context-aware time calculations (_contextNow)
- Schedule days computation with contextNow/realNow
- Current time row display logic
- Days to show calculations for week and month views
- Add ScheduleService tests for:
- getDaysToShow with reference dates
- getMonthDaysToShow with padding days
- buildScheduleDays parameter handling
- getDayClass with reference month support
- Fix test setup:
- Use TranslateModule.forRoot() for proper i18n support
- Add complete mock methods for ScheduleService
- Handle timing-sensitive tests with appropriate tolerances
- Use correct FH constant value (12) for time row calculations
- Remove unused CollapsibleComponent import from config-page
All 32 ScheduleComponent tests passing
Add hasTasksBatch() and getByIdBatch() methods to TaskArchiveService
for efficient bulk operations. Update ArchiveOperationHandler to use
the new batch API instead of direct archive access.
Performance improvement: Bulk operations now require only 2 IndexedDB
reads regardless of task count (was N×2 before).
Changes:
- Add TaskArchiveService.hasTasksBatch() for bulk existence checks
- Add TaskArchiveService.getByIdBatch() for bulk task retrieval
- Update _handleUpdateTasks() to use hasTasksBatch() method
- Add comprehensive unit tests (8 test cases)
- Verify archives loaded exactly once in tests
Related: Phase 2 implementation following previous optimization commit
Add Previous/Next/Today navigation buttons to schedule view:
- Navigate between weeks and months
- Today button returns to current date
- Previous button disabled when viewing today
- Task filtering: unscheduled tasks only appear in current week
- Tasks with dueDay/dueWithTime filtered by date relevance
- Sun icon only shows on actual today, not first displayed day
- Month view correctly highlights viewed month (not just current month)
Technical improvements:
- Add public methods to ScheduleService (getTodayStr, createScheduleDaysWithContext)
- Fix date mutations in navigation methods for immutability
- Remove redundant canNavigateToPast computed signal
- Pass both contextNow and realNow for proper task filtering
- Encapsulate private property access with public API
The implementation uses Angular signals for reactive navigation state and
ensures tasks are filtered correctly based on actual current week, not the
displayed week.
Reduce IndexedDB reads for bulk archive updates from N×2 to 2 total.
For 50 tasks: 100 reads → 2 reads (50x improvement).
Changes:
- Load both archives once in _handleUpdateTasks() instead of calling hasTask() N times
- Add hasTasksBatch() method for reusable batch existence checks
- Remove per-task event loop yielding (now only yield before write)
Performance: <100ms for 50-task batch (down from ~500ms)
- Add redirect_uri parameter to OAuth flow for mobile platforms
- Create OAuthCallbackHandlerService to handle deep link callbacks
- Register custom URI scheme (com.super-productivity.app://) in Android/iOS
- Add platform-specific UI for OAuth flow (automatic vs manual)
- Implement proper error handling for OAuth callback errors
- Add comprehensive unit tests for callback handler
- Fix memory leak by properly cleaning up event listeners
- Use IS_NATIVE_PLATFORM constant for consistent platform detection
Web/Electron continue using manual code entry (no regression).
Mobile (iOS/Android) now use automatic redirect with deep linking.
Fixes Dropbox OAuth authentication on iOS and Android platforms.
Add event loop yielding after hasTask() calls to prevent UI freezes
during bulk archive sync operations.
Root cause: TaskArchiveService.hasTask() loads the entire archive
(archiveYoung + archiveOld) from IndexedDB synchronously. When called
multiple times in sequence (e.g., during remote sync), this blocks the
main thread and causes browser freezes.
Changes:
- _handleUpdateTask: Add yield after hasTask() check
- _handleUpdateTasks: Convert Promise.all to sequential with yields
This fixes 12 out of 13 WebDAV archive sync E2E tests (92% pass rate).
Related changes:
- Commit 8c4daadcc: Parallelized archive task existence checks
- Commit 70946927a: Disabled worklog refresh effect
The test was failing because the Welcome tour dialog was blocking
the "Finish Day" button click. Added dismissTourIfVisible() helper
before archiving operations to prevent this.
Also updated operation-applier.service.ts to remove the yield after
dispatching remoteArchiveDataApplied since the effect that listened
to this action has been disabled.
Current status:
- Test progresses past both clients archiving successfully
- Tour dialog no longer blocks UI interactions
- Still investigating timeout at final state verification
Verifies that URLs ending with '/' correctly extract the path segment
as the attachment title without truncation.
Example: https://example.com/projects/ → title: "projects"
This test covers the bug fix in commit 2a8e7433b where the substring
offset was corrected from -2 to -1.
Cleanup after URL attachment feature changes that removed the
isEnableUrl config option (URLs are now always parsed).
Changes:
- Remove test for disabled URL config in add-task-bar-parser.service.spec.ts
- Remove test for disabled URL config in short-syntax.spec.ts
- Remove unused imports in archive-operation-handler.effects.ts
(Actions, WorklogService, remoteArchiveDataApplied, tap operator)
URLs ending with '/' were incorrectly truncating the last character
of the path segment when extracting attachment titles.
Example fix:
- Input: https://example.com/path/
- Before: "pat" (lost one character)
- After: "path" (correct)
Changed substring offset from -2 to -1 to properly remove only
the trailing slash character.
Convert planner from fixed 15-day view to endless scroller that loads 7 more days when scrolling to the last day. Uses IntersectionObserver for efficient visibility detection with proper cleanup.
Replace real timer delays with mocked timers in SuperSyncRestoreService
tests. Reduces 4 tests from 2-6 seconds each to <1 second each,
saving ~18-20 seconds from test suite execution time.
Add in-progress flag with iOS-specific delayed clearing to prevent the native share sheet from immediately reappearing after dismissal. On iOS, dismissing the share sheet fires window focus events that can re-trigger the share method. The 500ms delay prevents this re-trigger while remaining imperceptible to users.
Fixes bug where reminders kept popping up after clicking "Add to Today".
Tasks already scheduled for today were incorrectly filtered out, preventing
remindAt and dueWithTime from being cleared. Now includes these tasks when
isClearScheduledTime flag is true.
When user clicks "Add to Today" in the reminder dialog, the task's
scheduled time is now always cleared, even if it was originally
scheduled for a specific time today (e.g., "2:30 PM today").
This ensures tasks become "sometime today" without a specific hour
when added from the reminder dialog, while preserving the existing
behavior for other flows (context menu, drag-drop).
- Add isClearScheduledTime parameter to planTasksForToday action
- Update reminder dialog to pass isClearScheduledTime: true
- Update meta-reducer logic to clear dueWithTime when flag is set
- Add missing TranslateService and LanguageService mock providers to OperationLogMigrationService tests (14 failures)
- Use safe property access (optional chaining) for androidInterface in CapacitorReminderService logging (1 failure)
All tests now passing: 6460 SUCCESS