Commit graph

12428 commits

Author SHA1 Message Date
Johannes Millan
0775650e6e 17.0.0-RC.13 2026-01-21 22:12:01 +01:00
Johannes Millan
86850c711a fix(sync): restore entity from DELETE payload when UPDATE wins LWW conflict
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.
2026-01-21 21:36:26 +01:00
Johannes Millan
cfb1c656dd feat: change bottom nav order again 2026-01-21 21:19:56 +01:00
Johannes Millan
d13701e071 test(performance): add stress tests for bulk hydration and adjust timeout values 2026-01-21 21:06:19 +01:00
Johannes Millan
ff0acbdd37 fix(gitignore): correct screenshots directory path in .gitignore 2026-01-21 21:05:29 +01:00
Johannes Millan
a35331f4ff feat(schedule): restore always 7 days with horizontal scroll for week view 2026-01-21 21:00:37 +01:00
Johannes Millan
dee9faad4f fix(icons): add missing calendar icon for ICAL provider
- 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"
2026-01-21 20:50:15 +01:00
Johannes Millan
94e1550227 fix(tasks): hide close button in bottom panel on mobile
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.
2026-01-21 20:10:38 +01:00
Johannes Millan
054acbdf63 fix(e2e): resolve test failures and improve encryption UX
- 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
2026-01-21 19:53:00 +01:00
Johannes Millan
337afed482 fix(task-view-customizer): persist sort, group, and filter settings to localStorage
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.
2026-01-21 19:41:43 +01:00
Johannes Millan
4661b19f7a style(icons): change Material Icons to outlined style
Changes FILL variation setting from 1 (filled) to 0 (outlined) to render all Material Symbol icons in outlined style throughout the app.
2026-01-21 19:18:40 +01:00
johannesjo
1421151724 fix(ios): prevent keyboard from overlapping inputs
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
2026-01-21 17:45:14 +01:00
johannesjo
806dbc2dc3 fix(share): prevent iOS share sheet from reopening on dismiss
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.
2026-01-21 17:09:57 +01:00
johannesjo
09d86d8afb fix(electron): resolve macOS app quit not responding
- 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
2026-01-21 16:54:11 +01:00
Johannes Millan
4317e6575d perf(icons): implement lazy loading for Material Icons to reduce bundle size
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.
2026-01-21 15:52:00 +01:00
Johannes Millan
9e1116555c refactor(icons): use drag_indicator instead of drag_handle for better visual 2026-01-21 15:51:29 +01:00
Johannes Millan
c0fbf5ddd8 fix(icons): update schedule nav icon from early_on SVG to schedule Material Symbol 2026-01-21 15:47:59 +01:00
Johannes Millan
7329c1cf1e refactor(icons): replace SVG icons with Material Symbols
Replace custom SVG icons with Material Symbols equivalents:
- play.svg → play_arrow icon
- drag-handle.svg → drag_handle icon
- early-on.svg → schedule icon
- estimate-remaining.svg → hourglass_empty icon
- calendar.svg → (unused, removed)
- keep.svg → bookmark_add icon (unpinned state)
- keep-filled.svg → bookmark icon (pinned state)
- bottom-panel-open.svg → (unused, removed)

This reduces custom SVG assets and leverages the Material Symbols font
for consistent icon rendering across the application.
2026-01-21 15:37:59 +01:00
Johannes Millan
709e688d6d feat(icons): upgrade from Material Icons to Material Symbols
- Replace legacy Material Icons (2,008 icons) with Material Symbols (3,798 icons)
- Adds missing icons reported in issue #6079: robot_2, manufacturing, cognition, cognition_2, neurology
- Update font: MaterialIcons-Regular.ttf (349KB) -> material-symbols-outlined.woff2 (456KB)
- Update icon names constant with 1,790 additional icons (+89% increase)
- Create extraction script for future icon updates
- Configure filled style to match previous appearance

Resolves #6079
2026-01-21 15:30:19 +01:00
Johannes Millan
2392ecb091 feat(schedule): make week view navigation responsive to viewport width
- 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.
2026-01-21 15:30:18 +01:00
Johannes Millan
702f768784 docs: clarify test commit message convention 2026-01-21 14:34:45 +01:00
Johannes Millan
d2ab8e6482 refactor(schedule): consolidate scrollbars onto single parent element
- 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.
2026-01-21 14:33:31 +01:00
Johannes Millan
f4d3c61ec9 fix(schedule): make horizontal scrollbar always visible at viewport level
- 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
2026-01-21 14:30:24 +01:00
Johannes Millan
c3983fbdb2 fix(schedule): force horizontal scrollbar to always be visible
- 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
2026-01-21 14:30:24 +01:00
Johannes Millan
7a98831835 feat(schedule): add horizontal scroll for week view on narrow viewports
- 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
2026-01-21 14:30:24 +01:00
Johannes Millan
5c851e52d3 feat(mobile-nav): open drawer from right side to match button position
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)
2026-01-21 14:30:24 +01:00
Johannes Millan
b8d05a2aa7 fix(tests): remove non-existent taskIdsToUnlink from test expectations
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.
2026-01-21 14:30:24 +01:00
Johannes Millan
0e13e14520 fix(schedule): fix timezone issues when parsing ISO date strings
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.
2026-01-21 14:30:24 +01:00
Johannes Millan
996f7e1210 test(task-repeat-cfg): improve test coverage for repeatCfgId cleanup
- 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
2026-01-21 14:30:24 +01:00
Johannes Millan
6dba9237e2 fix(sync): prevent SuperSync accessToken overwrite by empty form values
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
2026-01-21 14:30:24 +01:00
Johannes Millan
0bd1bafcef fix(sync): prevent orphaned repeatCfgId during conflict resolution
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
2026-01-21 14:30:24 +01:00
Johannes Millan
97d59ffcb4 tests: fix 2026-01-21 14:30:24 +01:00
Johannes Millan
e673d74b55 test(schedule): add comprehensive unit tests for navigation
- 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
2026-01-21 14:30:24 +01:00
Johannes Millan
e43adba618 feat(archive): add batch methods for archive operations
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
2026-01-21 14:30:24 +01:00
Johannes Millan
bda98c954c feat(schedule): add navigation controls with week-aware task filtering
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.
2026-01-21 14:30:24 +01:00
Johannes Millan
269eb9952a perf(archive): optimize bulk archive operations with single load
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)
2026-01-21 14:30:24 +01:00
Johannes Millan
40b18c4693 fix(sync): implement OAuth redirect for Dropbox on mobile
- 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.
2026-01-21 14:30:24 +01:00
Johannes Millan
b59aa6b8f7 perf(sync): add event loop yielding in archive operation handler
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
2026-01-21 14:30:24 +01:00
Johannes Millan
90bdfe54e1 fix(e2e): dismiss welcome tour in archive sync test
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
2026-01-21 14:30:24 +01:00
Johannes Millan
ce615015c9 test(tasks): add unit test for URL basename with trailing slash
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.
2026-01-21 14:30:23 +01:00
Johannes Millan
92ed8322f5 chore: remove tests and imports for deleted isEnableUrl config
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)
2026-01-21 14:30:23 +01:00
Johannes Millan
22adb1df45 fix(tasks): correct URL basename extraction for trailing slashes
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.
2026-01-21 14:30:23 +01:00
Johannes Millan
c49209d364 perf(sync): parallelize archive task existence checks for bulk updates 2026-01-21 14:30:23 +01:00
Johannes Millan
c6ceaa5f6b feat(planner): implement endless scroll for future days
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.
2026-01-21 14:30:23 +01:00
Johannes Millan
2bcdd52037 perf(tests): use jasmine.clock() to speed up retry tests
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.
2026-01-21 14:30:23 +01:00
Johannes Millan
5a9f52ee62 fix(ios): prevent share overlay from reappearing after dismissal
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.
2026-01-20 17:07:24 +01:00
Johannes Millan
286e04834e fix(reminders): clear scheduled time when adding to today from dialog
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.
2026-01-20 17:07:24 +01:00
Johannes Millan
2844560ef8 refactor(tasks): remove isEnableUrl config, always enable URL parsing 2026-01-20 17:07:24 +01:00
Johannes Millan
853bbcf268 fix(reminders): clear scheduled time when adding to today from dialog
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
2026-01-20 17:07:24 +01:00
Johannes Millan
ff1f656dc2 test: fix 15 failing unit tests
- 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
2026-01-20 17:07:24 +01:00