docker-compose.e2e.yaml was failing when used standalone because the
supersync service only had a port override without a base definition.
Split into separate files so e2e:docker:webdav works without errors.
- Apply runtime default for isConfirmBeforeTaskDelete to ensure existing users get confirmation dialog
- Add takeUntil cleanup for dialog subscription in context menu
- Move _isTaskDeleteTriggered flag before delete call to prevent race condition
- Replace alert() with PFLog.warn() for non-blocking notification
- Change any to unknown type for better type safety
- Localize month names using DateAdapter instead of hardcoded English strings
- Remove duplicate monthNames array declaration (DRY)
Extract reusable heatmap component from activity-heatmap and add
a new repeat-task-heatmap that shows time spent history for
repeatable task instances in the configuration dialog.
Add handlers to autoFixTypiaErrors to fix validation errors for deprecated
fields that will be removed in future:
- metric.entities[*].obstructions, improvements, improvementsTomorrow
- improvement.hiddenImprovementBannerItems
These fields are now auto-fixed to empty arrays when missing/undefined,
allowing import of older backups without validation failures.
Apply GitHub's OR pattern to fix timing bug while preserving filterUsername.
Refactor getFreshDataForIssueTasks to call getFreshDataForIssueTask (DRY).
Fixes inconsistency from #5944 where only one method was updated.
When the app is backgrounded on Android, the focus mode countdown timer
now triggers a high-priority notification with alarm sound and vibration
when it completes. Previously, only the silent foreground service
notification was shown, causing users to miss timer completion.
Changes:
- Add completion notification channel with IMPORTANCE_HIGH
- Detect timer completion in FocusModeForegroundService
- Broadcast completion event to activity via LocalBroadcastManager
- Forward event to Angular via onFocusModeTimerComplete$ subject
- Handle native completion in effects to sync app state
Fixes#5923
Fixes#5921: When a task was rescheduled by removing the time and
changing only the date, the native Android alarm would still fire at
the original time. This was because removeTaskReminderSideEffects$
only removed the reminder from ReminderService but did not cancel
the native Android alarm.
Added explicit native Android alarm cancellation following the same
pattern used in clearRemindersOnDelete$, unscheduleDoneTask$, and
other similar effects.
Add service-level deduplication to prevent the same reminder from
triggering multiple notifications when the worker's 10-second polling
interval races with state updates.
The fix tracks recently processed reminder IDs and filters them out
before emitting. IDs are cleared when snoozing (to allow re-trigger at
new time) and auto-cleaned after 60 seconds to prevent memory leaks.
Closes#5925
Add isFocusWindow setting to reminder config, defaulting to false.
This prevents the app from stealing focus when reminders fire, which
was interrupting users working in other applications.
Closes#5922
Display weekday alongside date when tasks are grouped by scheduled date
(e.g., "Wed 1/15" instead of "2025-01-15"), making it easier to identify
weekends at a glance.
Closes#5941
Add optional setting to show confirmation dialog when deleting tasks
via keyboard shortcut (Backspace) or context menu. This prevents
accidental cascading deletions when users press Backspace expecting
browser-like "go back" behavior.
- Add isConfirmBeforeTaskDelete setting (defaults to true)
- Add confirmation dialog to TaskComponent.deleteTask()
- Add confirmation dialog to context menu deleteTask()
- Add setting checkbox in Settings > Misc
- Add unit and E2E tests for the feature
Closes#5942
supersync-models.spec.ts:
- Replace fragile inline tag deletion with robust retry-wrapped implementation
- Use toPass() with increasing intervals for automatic retries
- Properly expand Tags section before finding target tag
- Wait for tag to disappear as final verification
supersync-server-migration.spec.ts:
- Remove unnecessary waitForTimeout(500) before sync
- Existing waitForTask() calls handle synchronization properly
Add comprehensive E2E test that verifies legacy data (pre-operation-log
format) migrates correctly when the app starts. The test:
- Seeds legacy 'pf' IndexedDB database with test data before app loads
- Verifies backup file is downloaded during migration
- Validates all data migrates correctly including:
- Tasks with subtasks (parent-child relationships)
- Projects and tags
- Sync provider settings (WebDAV/SuperSync configs)
- Archive data (archiveYoung and archiveOld)
- Time tracking data and notes
Includes comprehensive fixture file with realistic legacy state format.
Verifies the core sync filtering mechanism that all 27 effects depend on:
- LOCAL_ACTIONS filters out actions with meta.isRemote: true
- LOCAL_ACTIONS passes through local actions (no meta or isRemote: false)
- ALL_ACTIONS receives both remote and local actions
This single test validates the filtering mechanism that prevents effects
from running during remote sync operations.
Replace sequential hasOp() + append() calls with batch operations:
- Use filterNewOps() to batch-check for duplicate ops
- Use appendBatch() to write multiple ops in a single transaction
- Reduces O(n) database calls to O(2) for each operation category
This optimization applies to:
- Remote-wins conflict ops
- Local-wins remote ops (stored then rejected)
- Non-conflicting piggybacked ops
Updated tests to expect appendBatch calls instead of append.
Adds E2E test for the scenario where an already-syncing client detects
that another client has uploaded a snapshot replacement:
1. Client A sets up sync, uploads initial data
2. Client B connects (no local data), downloads A's data
3. Client B creates a local task (not yet synced)
4. Client C connects with local data, resolves conflict with USE_LOCAL
5. Client B syncs and should detect the gap/snapshot replacement
This tests the fix for snapshot replacement detection in
file-based-sync-adapter.service.ts, which uses clientId comparison
when available and falls back to syncVersion comparison otherwise.
- auto-fix-typia-errors.ts: use unknown with type guards in path utilities
- data-repair.ts: add type-safe helper for entity state reset
- is-data-repair-possible.util.ts: use 'in' operator for property checks
- is-related-model-data-valid.ts: use MenuTreeTreeNode type instead of any[]
- sync.types.ts: use recursive Serializable type for SerializableObject
- sync-errors.ts: use IValidation<unknown> for validation result types
- dropbox-api.ts: use string | Record<string, unknown> for upload data
- persistent-action.interface.ts: add eslint-disable comment for justified any
When detecting snapshot replacement during op download, use a dual-strategy:
- If excludeClient is provided: use clientId comparison (more accurate)
- If excludeClient is undefined: fall back to syncVersion comparison
This fixes gap detection when another client uploads a fresh snapshot
that replaces all ops but keeps syncVersion at 1.
The ternary handles both scenarios:
- Unit tests: pass excludeClient, use clientId check
- Download service: passes undefined, uses syncVersion check
Add unit tests for LWWOperationFactory and StateSnapshotService to
improve test coverage. Optimize remote ops processing by replacing
sequential append calls with a single appendBatch call, reducing
N database transactions to 1.
Playwright throws ENOENT errors when finalizing trace artifacts for
manually-created browser contexts. This is a known issue when using
trace: 'retain-on-failure' with contexts created via browser.newContext().
The fix extends error handling to catch and log these cleanup errors
instead of failing the test after assertions have already passed.
The initial fix returned correct serverSeq after snapshot upload,
but snapshotReplacement gap detection still triggered incorrectly.
When sinceSeq equals syncVersion, we're in sync (our own upload),
not detecting another client's snapshot replacement.
Added E2E regression test for WebDAV sync to verify no repeated
conflict dialog after USE_LOCAL resolution.
Update test fixtures to use correct NgRx entity format (task, project,
tag, globalConfig) instead of invalid plural keys (tasks, projects).
This aligns tests with the payload validation added in 2d26228ca.
Rather than comparing the issues last update timestamp with the last comment timestamp, it now compares the issue last update timestamp with the task `issueLastUpdated` property. This avoids the latency between an update and the update being logged in the comments.
Fixes#5518
After uploading a snapshot (USE_LOCAL conflict resolution), set the local
seq counter to the new syncVersion instead of 0. This ensures the next
downloadOps call with sinceSeq > 0 does not return snapshotState, which
would trigger another LocalDataConflictError and repeated conflict dialogs.
- Add upgrade callback to ArchiveStoreService._init() to create archive
stores if this service opens the database before OperationLogStoreService
- Change null checks from `!== undefined` to `!= null` to handle both
null and undefined values from backup files
Remove debug logging that was added to trace the archive project
reference bug. The root cause (wrapped vs unwrapped payload format)
was fixed in commit 2d26228ca.
- Add decryptWithMigration() for migrating legacy PBKDF2 to Argon2id
- Add DecryptResult interface and DECRYPT_WITH_MIGRATION_FN token
- Update security comments in encryption.ts (remove vague TODO)
- Add user notification when deferred actions fail after retries
- Add DEFERRED_ACTION_FAILED translation key
Also includes temporary DEBUG logs for sync troubleshooting (to be removed).
- Fix BackupService to use unwrapped payload format (was wrapping in
{ appDataComplete: ... } while SyncHydrationService used unwrapped)
- Add shared payload utilities to operation.types.ts:
- FULL_STATE_OP_TYPES set for SyncImport, BackupImport, Repair
- extractFullStateFromPayload() handles both formats
- assertValidFullStatePayload() for runtime validation
- Update upload service to use shared utilities and validate payloads
- Add comprehensive unit tests for payload format consistency
- Add rollback logic to archive handler on IndexedDB failure
- Optimize getLatestFullStateOp() with reverse cursor iteration
- Add retry with exponential backoff for deferred actions
- Notify users when local changes are discarded due to stale state
- Reject pending ops in file-based sync when snapshot replaces state
- Remove stale SyncSafetyBackupService reference from startup test
When importing a backup file locally, archive data (archiveYoung and
archiveOld) was not being persisted to IndexedDB. This caused archived
tasks to be lost after page reload.
Root cause: ArchiveOperationHandler._handleLoadAllData() returns early
for local imports (isRemote=false), expecting the archive to be written
by the backup import flow - but this code never existed.
Fix: Add _writeArchivesToIndexedDB() to BackupService.importCompleteBackup()
that explicitly writes archive data to IndexedDB after dispatching the
loadAllData action.
- Add ArchiveDbAdapter injection to BackupService
- Add _writeArchivesToIndexedDB() private method
- Update misleading comment in ArchiveOperationHandler
- Add unit tests for archive persistence (7 tests)
- Add E2E tests for archive import persistence (3 tests)
- Add test fixture with archive data
The WebDAV E2E tests were failing on first run due to a race condition
where clicking the WebDAV option didn't always update the Formly form
model before waiting for the form fields to appear.
Changes:
- Verify mat-select displays "WebDAV" after clicking the option
- Add stabilization wait for Formly hideExpression to evaluate
- Increase timeouts (5s→8s in loop, 10s→15s final check)
- Add dialog-level retry as fallback when all attempts fail