Commit graph

18566 commits

Author SHA1 Message Date
johannesjo
6fbda9d25f fix(mac): regenerate icon without alpha channel to fix MAS white circle
The Mac App Store renders transparent icons differently than DMG distribution,
causing a white circle around the app icon. Regenerated icon-mac.icns from
sq2160x2160.png without alpha channel (hasAlpha: no, samplesPerPixel: 3).
2026-01-29 18:07:41 +01:00
Corey Newton
3aa21d751a
docs/wiki content v0.4 (#6257)
* docs(wiki): enhance keyboard shortcuts documentation

Added detailed descriptions of functional grouping for keyboard shortcuts, including categories such as Global, Navigation, Task management, and UI panels. Updated the Global Shortcuts section for clarity and included platform-specific differences, configurable vs reserved shortcuts, context-dependent behavior, and storage definitions. This improves user understanding of the shortcut system and its configuration.

* docs(wiki): add reference to Web App vs Desktop differences

Updated the 3.00-Reference and 3.02-Settings-and-Preferences documentation to include a new section on differences between the Web app and Desktop app, linking to the newly created [[3.05-Web-App-vs-Desktop]] page. This enhances clarity and provides users with a direct reference for understanding the distinctions.

* docs(wiki): expand short syntax documentation

Enhanced the 3.04-Short-Syntax.md file by detailing the four main short syntax forms for task metadata: tags, projects, time estimates, and due dates. Each syntax form now includes its purpose, grammar, valid and invalid examples, and additional parsing rules. This update improves user understanding of how to effectively utilize short syntax in task management.

* docs(wiki): add User Data reference to documentation

Included a new reference to [[3.06-User-Data]] in both the 3.00-Reference and _Sidebar.md files. This addition enhances navigation and provides users with direct access to information regarding user data management.

* docs(wiki): expand API documentation for Sync Server and Plugin API

Added comprehensive details on the Sync Server REST API and Plugin API, including authentication methods, endpoint descriptions, request/response schemas, error codes, rate limits, and data retention policies. This update significantly enhances the documentation, providing users with a clearer understanding of the APIs available for data synchronization and plugin development.
2026-01-29 17:35:38 +01:00
Johannes Millan
8428952004 fix(markdown): handle gapped checklists without breaking layout (#6244)
Use parse() instead of parseInline() for list items to handle block-level
tokens in loose lists. Strip <p> tags from task items to keep checkbox
and text on the same line.
2026-01-29 17:10:36 +01:00
Johannes Millan
51ad71d52a fix(android): keep app time value when native reports less to prevent data loss
When the native Android foreground service has less elapsed time than
the app (negative duration), the old code was incorrectly adding the
native value to the current time, corrupting the data.

Now when duration is negative (indicating the native service crashed
and restarted), we keep the app's higher value to prevent data loss,
update the native notification to show the correct time, and reset
the tracking interval to prevent double-counting.
2026-01-29 17:02:16 +01:00
Johannes Millan
0b47ef15e4 fix(android): persist time synced from native service to operation log
When syncing time from the Android foreground service after app resume,
addTimeSpent only updated local NgRx state but did not create an operation
in the log. This caused time loss if the app was closed before the next
flush cycle.

Now dispatches syncTimeSpent after addTimeSpent to ensure the synced time
is captured in the operation log and persisted to IndexedDB.

Fixes #6207
2026-01-29 16:44:53 +01:00
Johannes Millan
8ca21171f6 fix(task-repeat): prevent date overflow when calculating monthly repeat
Swap setDate(1) and setMonth() order to avoid JavaScript Date overflow
when moving to next month from dates like Jan 29 (Feb has only 28 days
in non-leap years, causing overflow to Mar 1).
2026-01-29 16:35:39 +01:00
Johannes Millan
400ca8c15d refactor(tasks): implement mutual exclusivity for dueDay/dueWithTime fields
When scheduling a task with a specific time (dueWithTime), dueDay is now
cleared instead of being set to the date string. Selectors check dueWithTime
first and only fall back to dueDay when dueWithTime is not set.

This ensures tasks scheduled for future days don't incorrectly appear in
today's list due to stale dueDay values.
2026-01-29 16:32:29 +01:00
Johannes Millan
f08083a3b1 fix(archive): make lastTimeTrackingFlush optional for backwards compatibility
Allows backups without lastTimeTrackingFlush field to pass Typia validation.
When the field is missing (legacy backup), the archive flush check treats it
as needing a flush, ensuring the field gets populated on first operation.

Fixes #6200
2026-01-29 16:32:29 +01:00
Johannes Millan
2a82754196 fix(op-log): add retry logic for IndexedDB open failures (#6255)
Add retry logic with exponential backoff when opening IndexedDB fails.
This helps recover from transient failures like file locks or temporary
I/O issues that can occur on Linux Snap.

Changes:
- Add _openDbWithRetry() with 3 retries and exponential backoff (500ms,
  1000ms, 2000ms delays)
- Create IndexedDBOpenError class with backing store error detection
- Show helpful error dialog with platform-specific recovery steps
- Add comprehensive tests for the new error class

Closes #6255
2026-01-29 16:32:29 +01:00
Johannes Millan
851d510f76 fix(electron): restore input focus after native dialogs
Fixes issue where input fields become read-only after interacting with
native confirm/alert dialogs in Electron. This is a known Electron bug
(electron/electron#19977, electron/electron#20821).

Creates confirmDialog() and alertDialog() wrappers that apply a
blur/focus workaround after dialogs close. All native confirm() and
alert() calls have been replaced with these wrappers.

Closes #6221
2026-01-29 16:32:29 +01:00
Johannes Millan
c10d7e4892 test(e2e): fix skipped tests and add encrypted backup import test
- Remove unfixable focus-mode pause/resume test (covered by 62 unit tests)
- Create encrypted backup fixture (test-backup-encrypted.json)
- Add 'Import encrypted backup while unencrypted sync is active' test
- Document why multiple encryption toggle test remains skipped
2026-01-29 16:32:29 +01:00
Johannes Millan
24d1145bc4 refactor(op-log): centralize error messages into constants
- Create op-log-errors.const.ts with error message constants
- Extract duplicate operation error messages (issue #6213)
- Extract initialization error messages
- Update all services and tests to use constants
- Improves type safety and maintainability
2026-01-29 16:32:29 +01:00
Johannes Millan
0d21c4da5a fix(tasks): prevent scheduled tasks from appearing in both today and tomorrow
Tasks scheduled for tomorrow via schedule dialog were appearing in both
today and tomorrow lists due to two bugs:

1. Reducer was setting dueDay=undefined for future days instead of the
   correct date string. Now uses getDbDateStr(dueWithTime) to always set
   the correct dueDay based on the scheduled time.

2. Selector fallback was including tasks in today based on dueWithTime
   even when dueDay was set to a different date. Now respects dueDay as
   authoritative - fallback only triggers when dueDay is not set.
2026-01-29 16:32:29 +01:00
Johannes Millan
1295057604 fix: improve code clarity and add data repair warnings (#6239)
- Rename misleading variable _allUndoneTaskIds to _allDoneTaskIds in
  nav-item.component.ts to match what selectAllDoneIds actually returns
- Add PFLog.warn when subtasks are promoted to standalone tasks during
  data repair due to missing parent tasks
2026-01-29 16:32:29 +01:00
Johannes Millan
7ed76c13a4 test(e2e): add E2E tests for recent sync bug fixes
Add comprehensive E2E tests covering SuperSync and WebDAV bug fixes:

- Archive preservation during "Keep Local" conflict resolution
- ConstraintError recovery (appliedOpIds cache invalidation)
- Encryption password preservation (form model race conditions)
- SYNC_IMPORT server state cleanup
- WebDAV rapid sync (false 412 error prevention)

Also adds archive/worklog helper functions to supersync-helpers.ts
and a test backup fixture with archived tasks.
2026-01-29 16:32:29 +01:00
Johannes Millan
c6cd0517f5 fix(sync): add retry logic for duplicate operation detection (#6213)
When filterNewOps returns ops as "new" using a stale cache but another
concurrent sync writes them before appendBatch runs, a "Duplicate
operation detected" error is thrown. Previously this error propagated
up and sync failed permanently.

This fix adds retry logic that:
1. Catches the "Duplicate operation detected" error
2. Retries filterNewOps (cache was invalidated by appendBatch)
3. Retries appendBatch with only truly new ops
4. Fails if retry also throws (prevents infinite loop)

Applied to all 4 vulnerable call sites:
- RemoteOpsProcessingService.applyNonConflictingOps()
- ConflictResolutionService.autoResolveConflictsLWW() (3 call sites)
2026-01-29 16:32:29 +01:00
Johannes Millan
e4fcad019d feat(sync): add warning for Local File sync about file-syncing tools
Warns users not to use Syncthing, Resilio, or similar tools to sync
the Local File sync folder, as they can cause conflicts. Suggests
WebDAV, Dropbox, or SuperSync for multi-device sync instead.

Closes #6249
2026-01-29 16:32:29 +01:00
Johannes Millan
b5cde1dcb5 fix(ios): hide Support us and Contribute links on iOS
Apple App Store guidelines prohibit external payment links.
Hide the donate button and contribute link when running on
iOS native platform.
2026-01-29 16:32:29 +01:00
Johannes Millan
27f12c6ad7 fix(e2e): improve SuperSync encryption test reliability
- Fix disableEncryption() to check if config dialog is still open
  before trying to close it (app's closeAllDialogs() may have closed it)
- Improve setupSuperSync() password dialog detection for Client B:
  - Prioritize dialogs and error states over sync success icon in race
  - Add handling for sync_error outcome
  - Wait longer for password dialog when sync shows success
- Fix "Enabling encryption" test to use fresh Client B after encryption
  is enabled (reconfiguring existing client triggers its own clean slate)
- Skip flaky "Multiple encryption state changes" test (core functionality
  covered by other tests)
2026-01-29 16:32:29 +01:00
Johannes Millan
f005e2bff3 fix(sync): add 1s buffer to WebDAV If-Unmodified-Since header
Fixes false 412 Precondition Failed errors during WebDAV sync when only
a single client is making changes. The root cause was a timestamp
precision mismatch: HTTP Last-Modified headers have second-level
precision, but some WebDAV servers store mtimes with millisecond
precision, causing the server to reject uploads as "modified".

Adding 1 second to the If-Unmodified-Since header compensates for this
precision difference while maintaining optimistic concurrency safety
(vector clocks still provide authoritative conflict detection).

Applied to both upload() and remove() methods for consistency.

Closes #6218
2026-01-29 16:32:29 +01:00
Johannes Millan
23f2c10b1e fix(ui): remove duplicate checkbox rendering in markdown
Remove renderer.checkbox override that caused checkboxes to render twice
in task lists. The listitem renderer already handles checkbox rendering
for task items.

Fixes #6228
2026-01-29 16:32:29 +01:00
Garrett04
1048184db4
feat(short-syntax): Automatically add a trailing space after inserting tag, project or time via Short Syntax. (#6118) (#6248)
Changes:
- Append blank space after itemValue in mention.directive.ts
- Update mention.directive.spec.ts to test expectations accordingly
2026-01-29 11:46:02 +01:00
Johannes Millan
2d835f9e73 17.0.9 2026-01-28 22:42:29 +01:00
Johannes Millan
1094cb0807 build: fix e2e test runs 2026-01-28 22:41:50 +01:00
Johannes Millan
821d05d495 17.0.8 2026-01-28 22:16:05 +01:00
Johannes Millan
08cfa556bd fix(ci): remove redundant grep-invert args from e2e command
The npm script chain already includes --grep-invert in the e2e script.
Passing it again from CI caused argument duplication, making playwright
interpret the extra args as a file pattern and find no tests.
2026-01-28 22:15:19 +01:00
Johannes Millan
819671de6d 17.0.7 2026-01-28 22:01:01 +01:00
Johannes Millan
4402575187 fix(sync): prevent archive corruption during "Keep local" conflict resolution
When a legacy client connected to WebDAV sync and chose "Keep local" in the
conflict dialog, the state validation would fail because:

1. downloadOps wrote REMOTE archives to IndexedDB immediately
2. LocalDataConflictError was thrown (archives already written!)
3. User clicked "Keep local"
4. forceUploadLocalState read archives from IndexedDB (now has REMOTE data)
5. NgRx still had LOCAL entity data (projects)
6. Validation failed: archived tasks referenced PROJECT_A which didn't exist

The fix:
- Remove archive writing from downloadOps (it was premature)
- Include archives in snapshotState response instead
- Write archives to IndexedDB during hydrateFromRemoteSync AFTER conflict
  resolution, when we're actually applying the remote state

This ensures local archives remain intact until the user explicitly chooses
to accept remote data.
2026-01-28 21:55:04 +01:00
Johannes Millan
a37d0eac4f fix(e2e): resolve flaky WebDAV tests and fix encryption test failures
- Add closeContextsSafely helper to handle Playwright trace file race conditions
  during browser context cleanup (fixes ENOENT errors on test retry)
- Fix enableEncryption method to properly handle dialog-based encryption flow
- Fix disableEncryptionForFileBased method: remove unnecessary expandAdvancedSettings,
  fix selector to match actual CSS class (.e2e-disable-encryption-btn)
- Update 5 test files to use closeContextsSafely for robust cleanup
2026-01-28 21:55:04 +01:00
Johannes Millan
be866b4325 refactor(tests): update settings page visibility checks and section count validation 2026-01-28 21:55:04 +01:00
Johannes Millan
a712e434b2 fix(sync): invalidate appliedOpIds cache on ConstraintError
When appendBatch() fails with ConstraintError, the appliedOpIds cache
was not invalidated, causing subsequent sync attempts to fail because
filterNewOps() kept returning operations that already existed in
IndexedDB from a previous partial sync.

Fixes #6213
2026-01-28 21:55:04 +01:00
Johannes Millan
db6de98582 fix: use providerManager.setProviderConfig to prevent file-based encryption password loss
Fix the same bug that was fixed for SuperSync in commit 1b8b18906.
When enabling encryption for file-based providers (WebDAV, LocalFile, Dropbox),
the code was calling provider.setPrivateCfg() directly which only saves to
IndexedDB but does not update the currentProviderPrivateCfg$ observable.

This caused the sync form to read stale config and overwrite the password
with an empty string on subsequent saves.

The fix uses this._providerManager.setProviderConfig() instead, which both:
1. Saves to IndexedDB
2. Updates the currentProviderPrivateCfg$ observable

Also adds comprehensive unit tests for FileBasedEncryptionService.
2026-01-28 21:55:04 +01:00
Johannes Millan
5512011aa2 refactor(task): rename button for toggling completion status 2026-01-28 21:55:04 +01:00
Johannes Millan
be11a4aef1 fix(sync): update currentProviderPrivateCfg$ observable when enabling encryption
When enabling encryption via EncryptionEnableService, the service was
calling syncProvider.setPrivateCfg() directly which only saved to
IndexedDB but did NOT update the currentProviderPrivateCfg$ observable.

This caused isEncryptionEnabled to remain false in the form model,
showing 'Enable Encryption' button instead of 'Change Password' button
after enabling encryption.

Fix: Use providerManager.setProviderConfig() which:
1. Saves to IndexedDB
2. Updates currentProviderPrivateCfg$ observable
3. Properly propagates isEncryptionEnabled state to the form

Also improved E2E changeEncryptionPassword() helper with better retry
logic and error messages.

Test results: 6856 unit tests pass, 6 E2E tests pass
2026-01-28 21:55:04 +01:00
Johannes Millan
f8e98225b7 refactor(sync): remove redundant Force Overwrite button from Change Password dialog
Both buttons did the same thing (clean slate + overwrite server), differing only
in unsynced ops handling. The Change Password button now always allows unsynced
ops since password change overwrites server data anyway.
2026-01-28 21:55:04 +01:00
Johannes Millan
47050c0658 fix(sync): preserve SuperSync encryption password from race condition
When changing the encryption password via dialog (EnableEncryption,
ChangePassword, HandleDecryptError), a race condition could cause the
new password to be overwritten by stale form model values that arrive
~2 seconds later via Angular's modelChange event.

Root cause: The form doesn't have a password field for SuperSync
(passwords are managed via dialogs), but updateSettingsFromForm()
would still use settings.encryptKey which could be empty/stale.

Fix: For SuperSync provider, always preserve savedEncryptKey from
IndexedDB unless the form explicitly provides encryptKey. File-based
providers (WebDAV, LocalFile, Dropbox) continue to use form values
as before since they have actual password fields in the form.

Added 6 comprehensive tests to prevent regression:
- Preserve SuperSync encryptKey when form has empty password
- Preserve SuperSync encryptKey when form has old password
- File-based providers still use form encryptKey (WebDAV)
- File-based providers use settings.encryptKey fallback
- Clear SuperSync encryptKey when encryption disabled
- Full race condition scenario simulation
2026-01-28 21:55:04 +01:00
Johannes Millan
07b1e97b97 fix(sync): ensure server deletes existing data before accepting new SYNC_IMPORT 2026-01-28 21:55:04 +01:00
Johannes Millan
2ed0e59d73 fix: emit legacy PBKDF2 deprecation warning only after successful decryption
Move the console.warn() from before decryption to after successful decryption.
This prevents false positive warnings when Argon2id decryption fails for
other reasons like wrong password, then falls back to legacy which also fails.
2026-01-28 21:55:04 +01:00
Johannes Millan
04f30d9af5 fix(sync): clear old full-state ops when new SYNC_IMPORT is applied
When a remote SYNC_IMPORT was downloaded and stored, older SYNC_IMPORT
operations were not being cleared. This caused the SyncImportFilterService
to use a stale import with a minimal vector clock, which filtered out
legitimate operations as CONCURRENT.

The fix:
- Added clearFullStateOpsExcept(excludeIds) method to clear old imports
  while preserving the newly stored one
- Call this method after successfully applying a new full-state op
- Added unit tests for the new behavior

This ensures sync continues working after password changes or when
multiple clients perform imports.
2026-01-28 21:55:04 +01:00
Johannes Millan
98f40093d6 fix(simple-counter): preserve order when reordering counters in settings
The updateAllSimpleCounters reducer was using adapter.upsertMany() which
doesn't preserve the order of items passed to it. Now explicitly sets
the ids array after upserting to maintain the desired order. Also marks
form as dirty when moving items for consistency with removeItem().

Fixes #6232
2026-01-28 21:55:04 +01:00
Johannes Millan
699c0348bc fix(ui): add missing checkbox renderer for marked options
The markedOptionsFactory was missing a dedicated checkbox renderer,
causing tests to fail when calling renderer.checkbox() directly.
2026-01-28 21:55:04 +01:00
Johannes Millan
e98023f70e refactor: unify encryption password blocks and move password input to dialog
- Unified two separate password info template blocks into single encryption status box
- Moved password input from sync form to enable encryption dialog
- Added password confirmation field with validation (min 8 chars, matching)
- Updated encryption dialog opener service to not require encryptKey parameter
- Added password-fields and password-error styles to common dialog SCSS
- Removed duplicate password input fields from SuperSync and file-based collapsibles
2026-01-28 21:55:04 +01:00
Johannes Millan
730addc181 feat(sync): close parent settings dialog after disabling encryption
- Add closeAllDialogs() method to EncryptionPasswordDialogOpenerService
- Call closeAllDialogs() after successfully disabling encryption
- Provides better UX by automatically closing the settings dialog
2026-01-28 21:55:04 +01:00
Johannes Millan
bd99d4a907 feat(sync): unify encryption status UI into a styled box
- Combine password info, change password, and disable encryption buttons
  into a single unified encryption status box at root form level
- Show for all sync providers when encryption is enabled
- Add btnStyle: 'stroked' support to FormlyBtnComponent for outlined buttons
- Remove duplicate buttons from SuperSync Advanced and COMMON SETTINGS collapsibles
- Add encryption-status-box CSS with border, padding, and background styling
- Add unified translation keys: BTN_CHANGE_PASSWORD, BTN_DISABLE_ENCRYPTION at FORM level
2026-01-28 21:55:04 +01:00
Johannes Millan
edcb2cbc18 fix(sync): translate WebDAV 412 error for retry mechanism
WebDAV sync was failing with persistent "412 Precondition Failed" errors
because RemoteFileChangedUnexpectedly was not being caught by the retry
logic in FileBasedSyncAdapterService. The retry mechanism only catches
UploadRevToMatchMismatchAPIError, which Dropbox and LocalFile providers
throw but WebDAV did not.

Translates RemoteFileChangedUnexpectedly to UploadRevToMatchMismatchAPIError
in WebdavBaseProvider.uploadFile() to enable proper retry behavior.

Fixes #6218
2026-01-28 21:55:04 +01:00
Johannes Millan
e3b517d177 fix: remove debug logging and fix isLoading reset in encryption dialog
- Remove debug SyncLog.log() from syncSettingsForm$ in sync-config.service.ts
- Use finally block to always reset isLoading in forceOverwrite() method
2026-01-28 21:55:04 +01:00
Johannes Millan
e260ceab80 fix(sync): allow force overwrite on encryption password 2026-01-28 21:55:04 +01:00
Johannes Millan
dbdd0878f5 refactor(sync): unify encryption state handling 2026-01-28 21:55:04 +01:00
Johannes Millan
7d84a36b4b fix(sync): reflect file-based encryption state in UI 2026-01-28 21:55:04 +01:00
Johannes Millan
64a3e64dd6 fix(sync): handle encryption password entry 2026-01-28 21:55:04 +01:00