The done-toggle was refactored from an inline SVG with class="done-toggle"
into a standalone Angular component <done-toggle>. Update all e2e selectors
from '.done-toggle' (class) to 'done-toggle' (element) to match.
- moveItemAfterAnchor: preserve current position when anchor is
concurrently deleted instead of appending to end (which corrupted
ordering on remote clients). For cross-list moves, append to end
as fallback to prevent data loss.
- Prevent double-write of deferred actions: track buffered actions in
a WeakSet and filter them in the effect so they are only written
once by processDeferredActions().
- Propagate skipDequeue through handleQuotaExceeded retry path to
prevent queue desync when deferred actions hit storage quota.
- Add e2e tests for concurrent delete + reorder sync scenarios.
- Suppress onboarding overlay for fresh Client B in WebDAV legacy
migration test (onboarding-backdrop was blocking sync button clicks)
- Update play indicator selector from .play-icon-indicator to
.play-indicator after done-toggle circle refactor removed the mat-icon
- Use markTaskDoneByKey in archive conflict tests for reliable done
state verification (click-based markTaskDone was silently failing)
- Replace drag handle with SVG done-toggle circle with checkmark draw-on animation
- Improve mobile UX: cdkDragStartDelay for touch, bottom-sheet context menu
- Move issue/repeat indicators from drag handle to tag-list as chips
- Add isFinishDayEnabled config toggle
- Reorder context menu: add sub task before duplicate, deadline as own entry
- Fix context menu not opening on swipe left
- Fix race condition in done toggle animation timeout
Remove the full welcome tour (Welcome, AddTask, DeleteTask, Projects,
Sync, IssueProviders, ProductivityHelper, FinalCongrats, StartTourAgain)
and keep only the KeyboardNav tour, triggered from the Help menu.
- Delete ShepherdComponent (auto-start on load)
- Strip shepherd-steps.const.ts to KeyboardNav steps only
- Simplify ShepherdService with _initPromise guard and fresh tour on re-open
- Remove 3 tour menu items from Help menu, keep keyboard tour
- Remove waitForEl/waitForElObs$ helpers (unused by KeyboardNav)
- Delete e2e/utils/tour-helpers.ts and all tour dismissal calls
- Clean up stale tour comments across e2e tests
- Fix pre-existing build error: add isFinishDayEnabled to AppFeaturesConfig
type, defaults, form toggle, component signal, and translations
- Add accounts-only pg_dump (users + passkeys) to backup script for
lightweight disaster recovery when clients still have data
- Add pipefail to backup script to catch silent dump failures
- Add test endpoint to simulate partial server revert (ops-after/:serverSeq)
- Add 6 e2e tests covering all disaster recovery scenarios:
complete data loss, partial revert, accounts-only restore (API + SQL),
full dump restore + reset account, and all-clients-lost recovery
- Add backup-and-recovery.md with setup, recovery procedures, and
decision tree
- Rewrite recurring-future-start-date test to set startDate via the
repeat dialog's model signal instead of the broken schedule-dialog
approach (mat-datepicker signals don't work from page.evaluate)
- Fix supersync encryption password-change test by clicking syncBtn
directly instead of triggerSync() which throws on error state before
the async decrypt dialog renders
- Fix flaky "Concurrent Delete vs. Update" test with deterministic
delete-wins assertion instead of permissive either-outcome check
- Delete supersync-encryption-enable-disable.spec.ts (4 tests) and
supersync-encryption-prompt-loop.spec.ts (2 tests) — these tested
optional encryption flows that no longer exist since encryption
became mandatory for SuperSync
- Wrong-password overwrite: remove Client A sync blocked by decrypt dialog
- Complex chain (4.1): rewrite without unreliable renameTask helper
- Delete vs Update race: fix assertions to expect delete wins
- Deleted task dueDay=today: same delete-wins fix, rename test
- Subtask conflicts: restructure so A marks done subtask from B
- Import tests: add syncImportChoice 'local' to preserve imported data
- Various other test fixes for reliability
- Fix importBackupFile() to wait for actual import completion via console
event instead of unreliable URL polling
- Fix setupSuperSync to respect syncImportChoice config in all code paths
- Add .first() to task locators that resolve to multiple elements on
project pages (backlog + active sections)
- Add decryptionFailedPassword config to handle "Decryption Failed" dialog
when server has old encrypted ops from before encryption changes
- Skip 1 test (unencrypted→encrypted) that needs server-side isCleanSlate fix
- Update error scenario mocks to use new OpUploadResponse format
({ results: OpUploadResult[] } instead of { piggybacked, rejectedOps })
- Use route.fetch() to get real op IDs from server for mock responses
- Add syncImportChoice option to SuperSyncConfig for import test control
- Skip 3 import-clean-server-state tests pending server-side isCleanSlate fix
Two root causes for failing E2E tests in CI:
1. Encryption button not visible after provider selection: The async
provider change listener loads config from IndexedDB and updates the
Formly model, but the 1000ms waitForTimeout was insufficient in CI.
Replace with polling (200ms intervals, 10s timeout) that waits for
either enable or disable encryption button to become visible.
Also removes ~130 lines of leftover debug logging from disableEncryption().
2. Drag-handle strict mode violation on parent tasks with subtasks:
task.locator('.drag-handle') matched both the parent's drag handle
and nested subtask drag handles. Add .first() to all drag-handle
locator usages to target only the parent task's own handle.
The dialog-enter-encryption-password now has two mat-flat-button buttons
when SuperSync is active (Save & Sync + Force Overwrite). The e2e locator
'button[mat-flat-button]' resolved to 2 elements causing Playwright strict
mode failures across all encryption-related SuperSync tests. Added
[color="primary"] to uniquely target the Save & Sync button.
* fixes to the profile system storage which worked unreliable across multiple storage backends
* fix tests
* fix issues with tests
* address copilot reviews
Fix deleteTask helper clicking on task-title, which entered edit mode
and caused Backspace to delete text instead of triggering the delete
shortcut. Use drag-handle click target instead.
Also add delete verification, resilient title assertions for known LWW
race, increased persistence waits, and extra sync rounds.
- Add E2E test for encryption + USE_LOCAL conflict resolution, validating
the double-encryption bug fix (b70a0e5634) end-to-end
- Add integration tests for partial trimming gap detection with
syncVersion-based check across multi-client scenarios
- Add integration tests for encrypted sync round-trips (ops, snapshots,
concurrent uploads)
- Fix systemic "waiting for navigation to finish" failures in WebDAV E2E
tests by adding noWaitAfter and waitForURL to SyncPage methods,
matching the pattern already used in SuperSyncPage
Remove duplicate LegacySyncProvider enum and use SyncProviderId everywhere.
The two enums had identical values but were separate types for historical
reasons, making the "Legacy" name misleading since providers are actively used.
The "Completed Tasks" collapsible uses @if which removes done task
elements from the DOM when collapsed. After syncing a done task to
other clients, the section may be collapsed, causing waitForTask and
isDone assertions to fail intermittently.
The "Completed Tasks" collapsible uses @if which removes task elements
from the DOM when collapsed. After sync convergence, Client A may have
the section collapsed while Client B has it expanded, causing the
consistency assertion to fail despite identical NgRx state.
Update createSimpleCounter() to use the new habits page flow after
the config page removal in 41027f3. Replace fragile waitForTimeout
calls with explicit sync completion checks in enableEncryption() and
increase timeouts in changeEncryptionPassword() and LWW conflict tests.
- Replace page.reload() with close-and-reopen pattern after backup
imports to prevent hangs with active sync connections
- Add proper sync-import-conflict dialog handling via Promise.race
- Fix browser context leak in provider-switch test
- Replace baseURL! non-null assertions with fallback pattern
- Replace fixed timeouts with element-based waits in cascade-delete
- Extract duplicate dialog-dismissal loop into dismissBlockingDialogs
helper in waits.ts
- Add missing WorkViewPage/SuperSyncPage imports in same-client test
- Replace any type with proper VectorClockEntry interface
Replace waitForLoadState('networkidle') with waitForAppReady() after
post-import page reloads and before task count verification. networkidle
only waits for network silence but not Angular bootstrap, loading
screens, or blocking dialogs - causing intermittent failures under load.
- Rewrite cascade delete test to avoid concurrent modification race
condition that the sync protocol correctly resolves by keeping both
sides' data (not a bug, just wrong test expectation)
- Add try/catch to dialog handlers in supersync.page.ts to prevent
"Cannot accept dialog which is already handled" errors when multiple
listeners compete
- Fix closeClient unhandled promise rejection when timeout wins
Promise.race
- cascade-delete: use context menu for project deletion instead of
non-existent settings page button
- multi-tab: set PLAYWRIGHT user agent on shared context and navigate
inactive tabs to about:blank to avoid multi-instance blocker
- token-expiry: fix route pattern to match actual API endpoint and
re-enter credentials after 401 clears them
- Cap sanitizeVectorClock values at 100M instead of MAX_SAFE_INTEGER (prevents adversarial clock inflation)
- Add early-exit optimization in compareVectorClocks for CONCURRENT detection
- Add test.setTimeout(180000) to token-expiry and lastseq-preservation E2E tests
- Remove dead code (unreachable guard) in compaction E2E test
- Extract shared config helpers (navigateToMiscSettings, toggleSetting, etc.) into e2e/utils/config-helpers.ts
- Fix Client B cleanup in provider-switch test (move to finally block)
- Add clarifying comments for raw SQL schema coupling, CORS header, and mergeRemoteOpClocks caller contract
- Extract enforceStorageQuota helper in sync routes to reduce duplication
- Add conflictType to conflict detection for structured error handling
- Add MAX_CLEANUP_ITERATIONS guard to freeStorageForUpload
Remove references to deleted docs/ai/ files, update stale comments about
protectedClientIds and pruning-aware comparison to reflect current REPLACE
semantics, fix MAX=30→20 heading, client_0..29→19 comment, use ?? over ||,
preserve lastSeq in server clean-slate path to prevent sequence reuse, and
add clarifying comments for mock limitations and validation guards.
- Use JWT_EXPIRY_PASSKEY (7d) for replaceToken instead of 365d magic link
expiry — token replacement is a security action, shorter lifetime is safer
- Replace localeCompare with locale-independent comparator in vector clock
tie-breaking to ensure deterministic behavior across environments
- Fix 5 additional stale MAX=30 references in docs and tests (now 20)
- Update authentication.md to reflect dual JWT expiry tiers
- Clean up isLikelyPruningArtifact references in docs and LEGACY_MAX in tests
With MAX_VECTOR_CLOCK_SIZE=20, server-side pruning requires 21+ unique
client IDs — extremely unlikely for a personal productivity app. The
isLikelyPruningArtifact heuristic was a backward-compat workaround for
the old MAX=10 era with acknowledged false positives. Remove it in favor
of strict clean-slate semantics: all CONCURRENT ops are filtered.
The same-client check remains — it's mathematically definitive (not a
heuristic) and correctly keeps post-import ops from the import's client.
Lower the cap to leave headroom for future increases and surface
size-related edge cases earlier. All pruning logic is MAX-agnostic
so this is a safe constant change with documentation updates.
At MAX=10, pruning triggered frequently enough (11+ unique client IDs from
reinstalls/new browsers) to require 4 defense layers compensating for
information loss: pruning-aware comparison, protected client IDs with
migration, isLikelyPruningArtifact heuristic, and same-client check.
At MAX=30, pruning almost never triggers (needs 31+ unique client IDs).
A 30-entry clock is ~500 bytes — negligible bandwidth. This allows removing
most defense layers while keeping two cheap backward-compat checks for old
10-entry pruned data still on servers.
Removed:
- Pruning-aware mode in compareVectorClocks (standard comparison now)
- Protected client IDs mechanism (storage, migration, preservation)
- selectProtectedClientIds function
- Clock normalization in SyncImportFilterService
Kept temporarily (backward compat with old 10-entry data):
- isLikelyPruningArtifact with LEGACY_MAX=10
- Same-client check (always mathematically correct)
When a client with an established vector clock (10+ entries) received a
remote SYNC_IMPORT/BACKUP_IMPORT with a fresh clock, mergeRemoteOpClocks()
merged the import's clock into the old clock instead of replacing it.
This caused clock bloat (11+ entries), which led to server-side pruning
dropping the import's entry (lowest counter). Other clients then saw
these ops as CONCURRENT with the import and discarded them.
Fix: In mergeRemoteOpClocks(), when a full-state op is present, use its
clock as the base instead of the existing local clock. Regular ops
continue to merge normally.
When vector clocks are at MAX_VECTOR_CLOCK_SIZE and have diverged due to
asymmetric pruning, ops from the same client that created the SYNC_IMPORT
were incorrectly classified as CONCURRENT and filtered. Same-client counter
comparison is mathematically definitive (monotonically increasing), so
CONCURRENT can only arise from pruning artifacts for the import's own client.
_convertToLWWUpdatesIfNeeded() now extracts the full entity from the
local DELETE payload, merges remote UPDATE changes on top, and produces
a flat entity payload that lwwUpdateMetaReducer expects. Previously it
only changed the actionType but kept the original MultiEntityPayload,
causing the meta-reducer to bail because entityData['id'] was undefined.
Also fixes _extractEntityFromDeleteOperation() to use
extractActionPayload() for consistent MultiEntityPayload unwrapping,
and increases E2E timing constants to ensure DELETE operations flush
to IndexedDB before sync triggers.
Two flakiness sources fixed:
- Click on task element could activate title inline editor, causing
Backspace to edit text instead of triggering delete. Now clicks the
drag handle which calls focusSelf() without entering edit mode.
- Replaced deleteTask helper with inline sequence to avoid wasting
2s of the 5s undo snackbar window on dialog-detection timeout.
Counter test helpers assumed mobile layout (.mobile-dropdown-wrapper)
but simulated clients use 1920x1080 viewport where counters render
inline in .counters-action-group.
- Remove serial mode and networkidle waits in legacy migration tests
- Reduce optional conflict dialog timeout from 5s to 2s
- Add dialog handler for Client B in first-sync-conflict USE_REMOTE test
- Add delay before sync check and lower threshold in burst sync test
- Add waitForArchivePersistence in concurrent same-task archive test
- Use closeContextsSafely, increase keyboard timing, and add
waitForStatePersistence in today-tag reorder tests