Commit graph

17407 commits

Author SHA1 Message Date
Johannes Millan
8b3dc445c4 test: fix e2e 2026-01-02 12:53:30 +01:00
Johannes Millan
ef8c8c21df fix(sync): fix sync E2E test failures and flaky encryption tests
operation-log.effects.ts:
- Move dequeue() inside the lock to prevent race condition with
  flushPendingWrites(). Previously, flushPendingWrites() could see
  queue=0 and return before IndexedDB write completed.
- Set entityId from entityIds[0] for bulk operations. Server requires
  entityId for non-full-state operations, but bulk actions like
  updateAllSimpleCounters only provide entityIds array.

supersync.page.ts:
- Fix flaky encryption checkbox tests caused by Angular Material
  checkbox starting in indeterminate "mixed" state before form loads
- Use retry logic with toPass() instead of single click + assert
- Click on label instead of touch target for more reliable interaction
- Increase timeout to 10s with smart intervals [500, 1000, 1500]ms
2026-01-01 17:14:59 +01:00
Johannes Millan
073eab162e fix(e2e): target specific tasks container in schedule page test
Use more specific selector to target only the scheduled tasks
container, avoiding ambiguity with planned-for-days tasks.
2026-01-01 15:21:01 +01:00
Johannes Millan
27641efeae feat(sync): add COUNTER_UPSERT action type
Add upsert action for SimpleCounter to support sync operations
that create or update counters in a single action.
2026-01-01 15:18:43 +01:00
Johannes Millan
ce7e201faa perf(test): use mock encryption in tests for faster execution
Encryption tests were slow (~0.5-2.4s each) due to Argon2id key
derivation (64MB memory, 3 iterations). This adds injectable
encryption tokens and fast mock implementations for tests.

Changes:
- Add ENCRYPT_FN/DECRYPT_FN injection tokens
- Update OperationEncryptionService to use injected functions
- Create mock-encryption.helper.ts with fast base64-based mock
- Update tests to use mock encryption via DI

Performance improvement:
- OperationEncryptionService tests: ~10s → 0.047s
- Service Logic Integration tests: ~1.7s → 0.08s

Real encryption still tested in encryption.spec.ts (3 tests, ~1s).
2026-01-01 15:14:21 +01:00
Johannes Millan
56266d28c6 fix(test): correct assertion for schema version skip limit test
The test incorrectly expected no snackbar to be shown for future version
operations within the skip limit. However, the service correctly shows a
WARNING (not ERROR) snackbar to prompt users to update when receiving ops
from newer schema versions.

Changed assertion from `not.toHaveBeenCalled()` to verify no ERROR
snackbar is shown, allowing the expected WARNING behavior.
2026-01-01 14:48:16 +01:00
Johannes Millan
91f0b6d135 feat(sync): add schema version mismatch warnings
Add user-facing notifications for schema version mismatches during sync:

- Show error and stop sync when receiving ops below MIN_SUPPORTED_SCHEMA_VERSION
- Show warning (once per session) when receiving ops from a newer schema version
- Operations from newer versions (within MAX_VERSION_SKIP) are still applied

Also adds 'WARNING' type to SnackService with appropriate styling.

Includes unit tests for all new behaviors.
2026-01-01 14:33:17 +01:00
Johannes Millan
1b2f5ff22c feat(supersync): add forgot password UI to login page
Add "Forgot Password?" link and form to the SuperSync server login page.
The backend endpoints already existed but had no UI entry point.

- Add forgot password button under login form
- Add email input form with "Send Reset Link" button
- Show generic success message (prevents email enumeration)
- Auto-return to login form after submission
2026-01-01 12:30:52 +01:00
Johannes Millan
2a9b84caf0 fix(api): add missing await in replace-token endpoint
The /replace-token endpoint was calling replaceToken() without await,
causing the response to return an unresolved Promise instead of the
actual token. This is the same class of bug as the email verification
race condition fixed in fd6499f13.

Also adds unit tests for the setCounter() fix from issue #5812:
- Tests for creating new counters with all mandatory fields
- Tests for updating existing counters (only countOnDay)
- Tests for incrementCounter and decrementCounter behavior
- Tests for error handling (invalid keys, negative values)
2026-01-01 12:24:32 +01:00
Johannes Millan
c087ffee8a fix(sync): prevent infinite rejection loop from corrupt operations
Root cause: Operations with `entityId: undefined` (corrupt ops) caused
an infinite rejection loop during sync:
1. Upload ops → Rejected with CONFLICT_CONCURRENT
2. Rejection handler creates merged ops (still with invalid entityId)
3. Upload merged ops → Rejected again
4. Loop continues forever

This prevented the sync status double checkmark from ever appearing
because hasPendingOps never became false.

Three-layer defense implemented:

1. Client: Guard time tracking dispatch (task.service.ts)
   - Changed `if (currentTask &&` to `if (currentTask?.id &&`
   - Prevents time tracking dispatch with undefined taskId

2. Client: Strengthen entityId validation (operation-log.effects.ts)
   - Check that entityId is truthy AND a string type
   - Log error with actual entityId value for debugging

3. Client: Cleanup corrupt ops on startup (operation-log-recovery.service.ts)
   - New cleanupCorruptOps() method marks corrupt ops as rejected
   - Runs during hydration to break existing infinite loops
   - Excludes bulk 'ALL' operations which legitimately lack entityId

4. Server: Validate entityId for all regular operations (validation.service.ts)
   - Reject ops with missing entityId (except full-state and bulk ops)
   - Full-state: SYNC_IMPORT, BACKUP_IMPORT, REPAIR
   - Bulk entity types: ALL, RECOVERY
   - Returns MISSING_ENTITY_ID error code (permanent rejection)
2025-12-31 17:29:25 +01:00
Johannes Millan
a86df2c356 fix(sync): prevent duplicate operation from aborting batch upload
When a duplicate operation was uploaded, PostgreSQL's unique constraint
violation (P2002) would abort the entire transaction, causing all
subsequent operations in the batch to fail with INTERNAL_ERROR.

Server-side fix:
- Add pre-check via findUnique before attempting insert
- Return DUPLICATE_OPERATION error code without aborting transaction
- Other ops in batch continue processing normally

Client-side fix:
- Handle DUPLICATE_OPERATION error code in rejected-ops-handler
- Mark duplicate as synced (not rejected) since server already has it
- Prevents infinite retry loop for legitimately uploaded ops

Adds tests for both server and client handling.
2025-12-31 16:39:55 +01:00
Johannes Millan
1775060b9d fix(supersync): use event listeners instead of inline onclick handlers
Replace inline onclick handlers with JavaScript event listeners to
comply with Content Security Policy (CSP). The helmet middleware's
scriptSrc directive blocks inline event handlers, preventing the
register tab and other buttons from working.
2025-12-31 14:35:33 +01:00
Johannes Millan
bb254dd07b Merge branch 'master' into feat/operation-logs
* master:
  fix(test): use dynamic date in year boundary test to avoid today collision
  fix(electron): reduce idle detection log verbosity
  fix(plugins): ensure setCounter creates valid SimpleCounter records
  feat(android): add quick add widget
  perf(android): prewarm WebView during idle time to speed up startup
  fix(ical): prevent race condition in lazy loader
  Merge branch 'master' into master

# Conflicts:
#	src/app/features/android/store/android.effects.ts
#	src/app/features/schedule/ical/get-relevant-events-from-ical.spec.ts
2025-12-31 14:20:58 +01:00
Johannes Millan
29ffa0f5d8 Merge remote-tracking branch 'origin/master'
* origin/master:
  Merge branch 'master' into master
2025-12-31 14:16:44 +01:00
Johannes Millan
a7d8a8b314 fix(today): set dueDay for tasks with dueWithTime scheduled for today (#5841)
Tasks with dueWithTime for today but no dueDay were not appearing in
the Today view because the selector only checks dueDay for membership.

Two fixes:
- Migration: Set dueDay when dueWithTime is for today during import
- Scheduler: Set dueDay conditionally instead of always clearing it

Closes #5841
2025-12-31 14:04:39 +01:00
Johannes Millan
6dea85c083 fix(test): use dynamic date in year boundary test to avoid today collision
The test was failing on Dec 31 because the hardcoded date '2025-12-31'
matched as "today", causing dateDisplay() to return just the time.
2025-12-31 14:04:22 +01:00
Johannes Millan
667353f41b test(e2e): add LWW delete vs update race test for TODAY_TAG
Add test verifying that when a task scheduled for today is deleted on
one client while updated on another, the recreated task (via LWW Update)
correctly appears in the TODAY view. This validates that
syncTodayTagTaskIds properly updates TODAY_TAG.taskIds during LWW
conflict resolution.
2025-12-31 13:49:13 +01:00
Johannes Millan
62d449a82f fix(electron): reduce idle detection log verbosity
Change frequent idle check logs from info to debug level to prevent
polluting system logs (e.g., /var/log/syslog) every 5 seconds.

Closes #5794
2025-12-31 13:42:18 +01:00
Johannes Millan
1529920c37 fix(plugins): ensure setCounter creates valid SimpleCounter records
The setCounter() method was using upsertSimpleCounter without providing
mandatory fields (title, type), causing DataValidationFailedError on
app reload when plugins created counters.

Now checks if counter exists first:
- Existing counters: only update countOnDay value
- New counters: create complete record with all required fields

Fixes #5812
2025-12-31 13:38:05 +01:00
Johannes Millan
ac43881d2d build: update types 2025-12-31 12:45:42 +01:00
Johannes Millan
4a89c05d32 feat(android): add quick add widget 2025-12-31 12:45:17 +01:00
Johannes Millan
fd6499f138 fix(security): address critical and high severity vulnerabilities
Security fixes implemented:

1. CRITICAL: Fix missing await in email verification (pages.ts:139)
   - verifyEmail() was called without await, causing race condition
   - Response was sent before verification completed

2. CRITICAL: Fix XSS vulnerability in password reset page (pages.ts)
   - Added safeJsonForScript() to properly escape tokens in JS context
   - JSON.stringify alone doesn't escape </script> sequences
   - Now escapes <, >, & as unicode (\u003c, \u003e, \u0026)

3. CRITICAL: Fix XSS in privacy HTML template (server.ts)
   - Added escapeHtml() function for all template interpolations
   - Prevents XSS if environment variables contain malicious content

4. HIGH: Enable Content Security Policy (server.ts)
   - CSP was disabled (contentSecurityPolicy: false)
   - Now enabled with strict directives:
     - default-src 'self', object-src 'none', frame-ancestors 'none'

5. HIGH: Block wildcard CORS in production (config.ts)
   - CORS_ORIGINS=* with credentials is a security vulnerability
   - Now throws error in production, warns in development

6. HIGH: Add password reset flow (auth.ts, api.ts, email.ts, schema.prisma)
   - Secure token generation with crypto.randomBytes(32)
   - 1-hour expiry, one-time use tokens
   - Revokes all sessions on password reset
   - Prevents email enumeration (same response for all cases)
   - Rate limited: 5 requests/15min for forgot-password
   - Rate limited: 10 requests/15min for reset-password

Test coverage:
- 45 new tests across 4 test files
- Tests for XSS prevention, CORS blocking, CSP headers
- Tests for password reset flow (API and unit level)
- Fixed pre-existing flaky boundary test in retention-config.spec.ts
2025-12-31 12:15:56 +01:00
Johannes Millan
25edb4ff1e perf(android): prewarm WebView during idle time to speed up startup
Load WebView native libraries during main thread idle time using
IdleHandler and WebSettings.getDefaultUserAgent(). This reduces
the 200-300ms freeze on first WebView creation.
2025-12-31 11:45:52 +01:00
Johannes Millan
d7cc365885 build: update package-lock.json 2025-12-31 11:35:59 +01:00
Johannes Millan
b46e1ff83c test: fix false-positive tests and syntax error
- Fix false-positive test in short-syntax.effects.spec.ts:176-178
  The test wrapped expect() in an if(emittedAction) block, causing it
  to pass silently when no action was emitted. Now properly asserts
  that emittedAction is defined before checking its type.

- Fix syntax error in get-relevant-events-from-ical.spec.ts:830-840
  Test used await inside synchronous expect().not.toThrow() wrapper,
  which is invalid TypeScript. Removed the wrapper since async tests
  will fail if they throw anyway.
2025-12-31 11:33:37 +01:00
Johannes Millan
6d82c1982d fix(ical): prevent race condition in lazy loader
Use Promise-based singleton pattern to ensure concurrent calls share
the same loading promise instead of triggering multiple imports.

Also fix pre-existing test bug using await in sync function.
2025-12-31 11:28:09 +01:00
Johannes Millan
3307c32339 fix(tag): implement self-healing for TODAY_TAG orphaned task IDs 2025-12-31 11:27:01 +01:00
Johannes Millan
c7d37ab773 Merge branch 'refs/heads/master' into feat/operation-logs
* refs/heads/master:
  perf: lazy load ical.js to reduce initial bundle size
2025-12-30 20:24:46 +01:00
Johannes Millan
1cb1e1c742 perf: lazy load ical.js to reduce initial bundle size
Move ical.js (~76KB) to a lazy-loaded chunk via dynamic import.
The library is only loaded when calendar integration features are used.

- Create ical-lazy-loader.ts for on-demand loading
- Make getRelevantEventsForCalendarIntegrationFromIcal async
- Update calendar-integration.service.ts to use switchMap
2025-12-30 20:20:07 +01:00
Johannes Millan
06e3136dd7 fix(sync): block selector-based effects during initial startup
Fixes TODAY_TAG conflicts on app startup when effects fire before
first sync completes. Effects like preventParentAndSubTaskInTodayList$
and repairTodayTagConsistency$ were creating operations during initial
data load that conflicted with remote operations.

Changes:
- Add isInitialSyncDoneSync() to SyncTriggerService for synchronous
  state checks in RxJS filter callbacks
- Extend skipDuringSyncWindow() to also block when initial sync is
  not done, not just during active sync operations
- Add debounceDuringStartup() operator for effects that should wait
  during startup but eventually run (vs being skipped entirely)
- Add comprehensive tests (31 new tests)
2025-12-30 18:57:31 +01:00
Johannes Millan
a97a881e27
Merge pull request #5791 from dXrayb/patch-12
Merge branch 'master' into master
2025-12-30 18:46:12 +01:00
Johannes Millan
8ebf16b958 fix(sync): add missing vector clock persistence in recovery, migration, and conflict resolution
Three related bugs where vector clocks weren't properly persisted:

1. Recovery path (operation-log-recovery.service.ts):
   - recoverFromLegacyData() saved vectorClock in state_cache snapshot
   - But getVectorClock() reads from separate vector_clock store
   - Fix: Add setVectorClock() after saveStateCache()

2. Migration path (operation-log-migration.service.ts):
   - Same issue as #1 for genesis operation during migration
   - Fix: Add setVectorClock() after saveStateCache()

3. Conflict resolution (conflict-resolution.service.ts):
   - autoResolveConflictsLWW() applied remote ops but didn't merge clocks
   - Subsequent local ops would have CONCURRENT clocks instead of GREATER_THAN
   - Fix: Add mergeRemoteOpClocks() after markApplied()

Without these fixes, operations created after recovery/migration/conflict
resolution could have incomplete vector clocks, causing:
- Incorrect filtering by SyncImportFilterService
- Server rejection as CONFLICT_STALE or CONFLICT_CONCURRENT

Adds regression tests for all three fixes.
2025-12-30 15:33:57 +01:00
Johannes Millan
132ae057d3 style(progress-bar): make bottom spinner more subtle, hide during sync
- Remove explicit sync progress bar trigger from pfapi.service.ts
- Add URL filtering to HTTP interceptor to skip sync-related requests
  (dropboxapi.com, /sync/, /ops, /snapshot, /restore-points)
- Style badge: smaller font (10px), reduced padding, no shadow, opacity
- Reduce progress bar opacity to 0.3 for subtler appearance

The top nav sync button spinner is now the only sync indicator.
2025-12-30 15:33:00 +01:00
Johannes Millan
431290c170 fix(sync): prevent data loss from stale vector clocks during SYNC_IMPORT hydration
Two bugs caused data loss after app restart with SuperSync:

Bug 1: Clock merge happened AFTER loadAllData dispatch
- Operations created during loadAllData (e.g., TODAY_TAG repair) got stale clocks
- Server rejected these as CONFLICT_STALE
- Fix: Call mergeRemoteOpClocks() BEFORE store.dispatch(loadAllData())

Bug 2: CONFLICT_STALE treated as permanent rejection
- Stale operations were marked rejected instead of being resolved via merge
- Fix: Handle CONFLICT_STALE the same as CONFLICT_CONCURRENT

Adds unit tests for both bugs and E2E regression tests to prevent recurrence.
2025-12-30 14:07:56 +01:00
Johannes Millan
5474c1a38f test: fix flaky timeouts by converting to fakeAsync/tick
Convert setTimeout-based "no emission" tests to use fakeAsync/tick for
more reliable execution under CI load:

- project.effects.spec.ts: Convert 2 tests (50ms → fakeAsync)
- short-syntax.effects.spec.ts: Convert 2 tests (100ms → fakeAsync)
- operation-log.effects.spec.ts: Convert 10 tests (10-20ms → fakeAsync)

Also improve task-due.effects.spec.ts documentation explaining why tests
are skipped (pfapi-config.ts eagerly instantiates Dropbox SDK at module
load time, requires architectural refactor to lazy-load sync providers).
2025-12-30 13:19:10 +01:00
Johannes Millan
76f58eb303 test: fix unit 2025-12-29 23:20:12 +01:00
Johannes Millan
ce58113ad0 fix(e2e): dismiss dialog backdrop before retry sync in error handling test
After simulating network failure and restoring network, a dialog backdrop
could remain visible and intercept clicks on the sync button. This caused
the retry sync to fail with a timeout.

Add check for `.cdk-overlay-backdrop` and press Escape to dismiss any
open dialogs before triggering the retry sync.
2025-12-29 22:54:26 +01:00
Johannes Millan
8408ac5c0e fix(lint): change hydration guard and entity registry rules from warn to error
- Change local-rules/require-hydration-guard from warn to error
- Change local-rules/require-entity-registry from warn to error
- Improve require-hydration-guard rule to skip effects with { dispatch: false }
  since they only perform side effects (audio, UI) and never dispatch actions
- Add skipWhileApplyingRemoteOps() guard to autoShowOverlay$ effect
- Add skipWhileApplyingRemoteOps() guard to triggerIdleWhenEnabled$ effect
- Add eslint-disable comments to test files that intentionally test
  unknown entity type handling
2025-12-29 22:35:40 +01:00
Johannes Millan
55484faffd fix(e2e): fix WebDAV sync tests that were failing
Two test fixes:

1. webdav-sync-full.spec.ts - "should sync data between two clients"
   - After multiple reloads in the deletion test section, Client B's
     sync state became stale and couldn't receive new tasks
   - Fix: Create a fresh Client B context for the conflict resolution
     section instead of reusing the stale one

2. sync.page.ts - setupWebdavSync combobox selector
   - The role-based selector getByRole('combobox', { name: 'Sync Provider' })
     was unreliable across different test runs
   - Fix: Use the existing providerSelect locator which was already
     validated to be visible, instead of creating a new role-based selector
2025-12-29 22:33:24 +01:00
Johannes Millan
6c098b6eaa Merge branch 'master' into feat/operation-logs
Resolve conflict in webdav-sync-expansion.spec.ts:
- Use simplified sync verification without reload (sync updates NgRx directly)
- Test: B marks task done -> sync -> verify A sees task as done
2025-12-29 21:54:15 +01:00
Johannes Millan
c9a25a988d perf(sync): skip download when all ops fit in piggyback response
Optimize sync to reduce round-trips when remote changes are small:

- Increase PIGGYBACK_LIMIT from 100 to 500 ops on server
- Skip download call when server confirms hasMorePiggyback === false
- CRITICAL: Only skip when hasMorePiggyback is explicitly false, not undefined
  (undefined means no upload happened, so we must still download)

This reduces sync from 2 round-trips to 1 when ≤500 ops are pending,
improving perceived sync performance.

Add 5 unit tests for the skip-download optimization logic.
2025-12-29 21:52:03 +01:00
Johannes Millan
cc593a324a test(webdav): improve stability of sync e2e tests
- Add retry loop for deletion sync to handle eventual consistency
- Fix done state sync test to handle Done Tasks section
- Add task order sync test
- Add time tracking sync test (skipped - complex persistence)
2025-12-29 20:38:06 +01:00
Johannes Millan
6865424f96 fix(sync): prevent removeTasksFromTodayTag with empty taskIds
The removeOverdueFormToday$ effect could dispatch removeTasksFromTodayTag
with an empty taskIds array when overdue tasks exist but none are present
in todayTaskIds (due to stale state or sync timing). This caused the
operation-log validation to fail with "missing entityId/entityIds" error,
preventing the operation from being persisted and causing missing tasks
on synced clients.

Add early return when overdueIds is empty and filter null actions.
2025-12-29 20:08:22 +01:00
Johannes Millan
016e680c5f fix(sync): fix simple counter sync and quota exceeded handling
- Add null checks in simple-counter.reducer.ts to prevent crashes when
  entity doesn't exist (fixes "Cannot read properties of undefined")
- Add missing entityIds to updateAllSimpleCounters action - was causing
  operation log to skip persistence
- Add try-catch in operation-log-upload.service.ts to show alert when
  storage quota exceeded (413 error)
- Fix supersync-network-failure.spec.ts test timing - route interception
  must happen before task creation
- Add documentation to CLAUDE.md for running supersync tests with
  real-time output using --reporter=line
2025-12-29 17:09:46 +01:00
Johannes Millan
a7cd442551 fix(op-log): fix operation flush timeout in daily-summary tests
Two fixes for supersync E2E test failures:

1. Remove race condition in operation-log.effects.ts:
   - Removed isApplyingRemoteOps() filter that caused actions enqueued
     by meta-reducer to be filtered out if sync started before effect ran
   - Meta-reducer already handles buffering during sync, so this filter
     was redundant and caused queue drain timeouts

2. Allow bulk operations with entityType 'ALL' to skip entityId validation:
   - [Archive] Flush Young to Old action has entityType 'ALL' but no entityId
   - Previous validation returned early without dequeuing, causing stuck queue
   - Now dequeues before early return and allows entityType 'ALL' to proceed

3. Fix URL pattern in supersync-daily-summary.spec.ts:
   - Changed from /(active\/tasks|tag\/TODAY\/tasks)/ to /(active\/tasks|tag\/TODAY)/
   - App sometimes navigates to tag/TODAY without /tasks suffix
2025-12-29 15:11:30 +01:00
Johannes Millan
efe8bea72d fix(e2e): fix archive-subtasks tests for TODAY tag virtual membership
The tests were failing because:
1. The backup file has tasks with dueDay in the past (2024-12-01)
2. TODAY tag is a "virtual tag" where membership is determined by
   task.dueDay === today, not by taskIds
3. When all tasks are done, planning mode activates and hides the
   Finish Day button

Fix by:
- Navigate to INBOX project to view and mark tasks as done
- Navigate to TODAY tag to access the Finish Day button
- Exit planning mode by clicking "Ready to work!" if visible
- Then proceed with Finish Day flow
2025-12-29 13:20:44 +01:00
Johannes Millan
24ef57ead1 fix(test): use valid ActionType enum values in operation-log.effects tests
Tests were using fake action types like '[Task] Update Task' which don't
exist in the ActionType enum. The effects code calls devError() for
unknown action types, which in test mode causes errors and timeouts.

Replace fake action types with valid enum values:
- TASK_SHARED_UPDATE, TASK_SHARED_ADD, TASK_SHARED_DELETE

Fixes 24 failing tests in npm run test:tz:ci
2025-12-29 13:08:51 +01:00
Johannes Millan
26735c7f74 build: update gitignore 2025-12-29 13:02:36 +01:00
Johannes Millan
c4f536471a test(webdav): add e2e tests 2025-12-29 12:48:36 +01:00
Johannes Millan
de192ab446 fix(op-log): add defensive validations and improve test coverage
- Add devError when action is missing entityId/entityIds (skips persist)
- Add devError when action.type is not in ActionType enum (warning only)
- Add devError when deferred actions buffer exceeds 10 items
- Add unit tests for CLIENT_ID_PROVIDER injection token

These changes catch programming errors early during development and
help identify sync issues (stuck/slow sync) via buffer size warnings.
2025-12-29 12:38:22 +01:00