Fixes#6021
When users manually added subtasks to markdown files that referenced
non-existent parent tasks, the plugin would crash due to unsafe null
assertions and missing validation. This made the plugin permanently
disabled and required creating a new markdown file to recover.
Changes:
- Added parent ID validation before creating operations
- Orphaned subtasks are now converted to root tasks with warnings
- Added comprehensive error handling with user notifications
- Plugin stays enabled even after sync errors
- Added 15 new tests (689 lines) for orphaned subtask scenarios
- Fixed all existing tests to support new error notifications
The fix implements defense-in-depth:
1. Validation layer: Check parent IDs exist before operations
2. Error handling: Catch and report errors without crashing
3. User feedback: Clear notifications about issues
4. Data preservation: No data loss, orphans become root tasks
- Remove placeholder address text from privacy policies (DE/EN)
- Expand HTML privacy policy with full GDPR disclosures:
- Legal bases (Art. 6), data subject rights (Art. 15-22)
- Supervisory authority, retention periods, DPA info
- Cookies/tracking and automated decision-making sections
- Align HTML terms with German ToS:
- Add proper termination notice periods (2 weeks/good cause)
- Add 6-week notice for ToS amendments
- Add consumer withdrawal rights (14 days)
- Add ODR platform link and jurisdiction info
The plugin was using a custom vite.config.ts that generated separate
JS/CSS files with broken relative paths. When loaded in a blob URL
iframe, these paths couldn't resolve.
- Switch to @super-productivity/vite-plugin which inlines assets
- Fix title from "Procrastination Buster" to "AI Productivity Prompts"
When uploading BACKUP_IMPORT via uploadSnapshot(), the client's op.id
was not sent to the server. The server would generate its own ID,
causing the client to not recognize the operation when downloaded later.
This led to data loss as the old backup state would be re-applied.
Changes:
- Add opId parameter to uploadSnapshot() interface
- Pass op.id from operation-log-upload.service.ts
- Send opId in SuperSync API request payload
- Server uses client's opId instead of generating new one
- Add E2E test to verify ID matching
The fix is backwards compatible - legacy clients without opId still work
as the server falls back to uuidv7() when opId is not provided.
Add explicit per-route rate limits to /verify-email and /magic-login
endpoints to address CodeQL security alerts. These endpoints previously
relied only on global rate limiting.
The shared-schema package was not being built before lint and test
steps in CI, causing "Cannot find module '@sp/shared-schema'" errors.
Changes:
- Add shared-schema:build script to package.json
- Update prepare script to build shared-schema first
- Add shared-schema to build-packages.js for completeness
The previous commit changed module resolution to bundler mode which
fixed Angular tests but broke Node.js ESM imports in super-sync-server.
Switch to tsup bundler that outputs both ESM (.mjs) and CJS (.js) formats,
with proper exports field to support both environments:
- CJS for super-sync-server (Node.js commonjs)
- ESM for Angular/webpack bundler imports
- Change shared-schema tsconfig to use bundler moduleResolution
- Remove .js extensions from imports (incompatible with bundler mode)
- Fix tsconfig.spec.json to include both package path mappings
The shared-schema package was using NodeNext module resolution which
requires .js extensions in imports. Since the main Angular project
uses bundler resolution and imports directly from source files,
this caused webpack to fail finding the modules during tests.
npm 10.9.4 bundled with node:22-alpine handles optional peer
dependencies differently than npm 11, causing build failures
when resolving vitest peer dep from @angular-devkit/build-angular.
- Fix TypeScript error in passkey.ts (Buffer to Uint8Array conversion)
- Update Dockerfile.test to use npm install instead of npm ci for workspace sync
- Update E2E test to not setup sync before import
- Add additional unit tests for sync-wrapper.service
Note: E2E tests for sync import conflict dialog need further work due to
automatic upload of SYNC_IMPORT when sync is enabled. The core dialog
functionality is implemented and unit tested.
When all remote operations are filtered due to a local SYNC_IMPORT
(e.g., user changed sync account), show a dialog offering three options:
- Use My Data: Push local state to server (forceUploadLocalState)
- Use Server Data: Accept server state (forceDownloadRemoteState)
- Cancel: Abort sync for now
Changes:
- Extend SyncImportFilterService to return filteringImport operation
- Extend RemoteOpsProcessingService to return filter metadata
- Create DialogSyncImportConflictComponent with Material Dialog
- Create SyncImportConflictDialogService to open the dialog
- Integrate dialog trigger into OperationLogSyncService
- Add translation keys to en.json
- Add comprehensive unit tests (86 tests passing)
- Add E2E test for dialog flow
Also increases maxKeys threshold in super-sync-server validation
to 500K to handle archives with 300K+ keys.
- Add vite-plugin-solid to procrastination-buster plugin dependencies
- Wrap Uint8Array in Blob for fetch body to fix TypeScript error with newer @types/node
Add automatic dark mode support using prefers-color-scheme media query:
- Dark backgrounds: #1a1a1a (page), #2d2d2d (cards)
- Light text: #e5e5e5 (primary), #a3a3a3 (secondary)
- Adjusted primary color for dark: #4db8e8 (7.74:1 contrast)
- Dark variants for warning/success/error message boxes
- Button backgrounds kept darker for white text contrast
Also add consistent link styling for Terms/Privacy links:
- Normal, visited, and hover states use primary color
- Works in both light and dark modes
All colors verified to meet WCAG AA 4.5:1 minimum contrast.
Update CSS color variables to meet WCAG AA 4.5:1 contrast ratio:
- --primary: #0c96e2 → #0077b6 (4.87:1 on white)
- --text-light: #666666 → #595959 (7.00:1 on white)
- --success: #4caf50 → #2e7d32 (5.13:1 white on bg)
- --error: #f44336 → #c62828 (5.62:1 white on bg)
- .warning-box: #856404 → #6d5200 (6.63:1 on #fff3cd)
All text elements now meet WCAG AA accessibility requirements.
Security:
- Add rate limiting (10 req/5min) to GET /snapshot endpoint
- Prevents DoS via CPU-intensive snapshot generation
Consistency:
- Add 30s timeout to download transaction (matches other sync transactions)
Test robustness:
- Fix weak encryption test - always assert error on wrong password
- Update lww-update.meta-reducer tests to use OpLog instead of console
Defensive coding:
- Add entity-not-found warnings in LWW meta-reducer for sync race conditions
- Log when project/tag/parent task deleted before LWW update arrives
Code quality:
- Standardize logging to OpLog in lww-update.meta-reducer
- Document LWW action types as intentionally not in ActionType enum
- Create e2e-constants.ts for centralized E2E test timeouts/delays
- Extract createProjectReliably helper to supersync-helpers.ts (DRY)
* master:
refactor(dialog): remove unused OnDestroy implementation from DialogAddNoteComponent
fix(calendar): poll all calendar tasks and prevent auto-move of existing tasks
docs: add info about how to translate stuff #5893
refactor(calendar): replace deprecated toPromise with firstValueFrom
build: update links to match our new organization
add QuestArc to community plugins list
feat(calendar): implement polling for calendar task updates and enhance data retrieval logic
fix(heatmap): use app theme class instead of prefers-color-scheme
fix(focus-mode): start break from banner when manual break start enabled
feat(i18n): connect Finnish and Swedish translation files
refactor(focus-mode): split sessionComplete$ and breakComplete$ into single-responsibility effects
Fixing Plugin API doc on persistence
# Conflicts:
# src/app/features/issue/store/poll-issue-updates.effects.ts
# src/app/t.const.ts
1. Add DELETE /api/sync/data route tests to sync.routes.spec.ts:
- Test successful deletion returns { success: true }
- Test 401 without authorization
- Test uploading new data works after reset
2. Fix passkey.spec.ts failures (4 tests):
- Add missing passkey.findUnique mock for credential lookup
- Update test expectations for discoverable credentials
(no allowCredentials - implementation changed)
3. Fix password-reset-api.spec.ts failures (12 tests):
- Exclude from vitest - tests routes that don't exist
- Server uses passkey/magic link auth, not password auth
All 412 tests now pass.
Add comprehensive unit tests for the reset account functionality:
- Test that deleteAllUserData removes all operations for a user
- Test that new operations can be uploaded after reset
- Test that resetting one user doesn't affect other users' data
Also adds missing userSyncState.deleteMany mock to the test setup.
Add a dedicated Account Settings page to the SuperSync server web interface:
- Move Reset Account and Delete Account buttons from token display to
separate Account Settings page for better UX
- Reset Account clears all synced data but keeps account active
- Delete Account permanently removes account and all data
- Add E2E tests for account reset functionality
Closes#5848
Bug fix:
- Fix vector clock cache staleness in multi-tab scenarios by clearing
cache when acquiring operation write lock. Each browser tab has its
own in-memory cache, so Tab B's cache could be stale if Tab A wrote
while Tab B was waiting for the lock.
Shared code extraction (client/server consistency):
- Extract vector clock comparison to @sp/shared-schema
- Client wraps shared impl with null handling
- Server imports directly from shared
- Extract entity types to @sp/shared-schema
- Single source of truth for ENTITY_TYPES array
- Removes duplicated "must match" comments
Files:
- packages/shared-schema/src/vector-clock.ts (new)
- packages/shared-schema/src/entity-types.ts (new)
- src/app/op-log/store/operation-log-store.service.ts (cache clear)
- src/app/op-log/capture/operation-log.effects.ts (call cache clear)
- Email is now shared between login, register, and lost passkey forms
- Email persists to localStorage across page reloads
- Typing in any email field updates all others in real-time
Existing passkeys were stored with double-encoded credential IDs due to
a bug where SimpleWebAuthn's credentialInfo.id (UTF-8 bytes of base64url
string) was stored directly instead of being decoded to raw bytes first.
This migration script converts existing passkeys from the old format
(ASCII bytes of base64url string) to the correct format (raw credential
ID bytes).
Run with: npx ts-node prisma/migrations/migrate-passkey-credentials.ts
SimpleWebAuthn's credentialInfo.id is a Uint8Array containing the
base64url-encoded credential ID as UTF-8 bytes, NOT the raw credential
ID bytes. We were storing these ASCII bytes directly, then encoding
them again as base64url during lookup.
During login, the browser sends the original base64url string, which
we decoded to raw bytes - but this didn't match the double-encoded
value stored in the database.
The fix decodes the base64url string from credentialInfo.id before
storing:
1. Convert Uint8Array to UTF-8 string (the base64url string)
2. Decode base64url to get raw credential ID bytes
3. Store raw bytes in database
Now login correctly looks up by raw credential ID bytes.
With discoverable credentials, the user can select any passkey for
this RP. We need to look up the passkey by the credential ID that
comes back from the browser, not by the email the user entered.
This fixes the 401 error when the user's passkey credential ID
doesn't match what we expected based on email lookup.
Instead of providing allowCredentials (which requires the browser to
match specific credential IDs), let the browser discover all resident
credentials for this RP automatically.
This matches how passkeys.io works and should fix the "no passkeys
available" issue on mobile.
Log the full credential ID (both hex and base64url) during:
- Registration: to see exactly what's being stored
- Login: to see exactly what's being retrieved from DB
This will help debug why passkeys aren't being found during login.
The recovery page was using the old SimpleWebAuthn API format:
startRegistration(options)
Updated to use the v13 format:
startRegistration({ optionsJSON: options })
This was causing "nothing happens" when clicking Register New Passkey.
The server was updated to @simplewebauthn/server v13.2.2 but the
browser library was still at pre-v13. The v13 API expects options
wrapped as { optionsJSON: options }, causing allowCredentials to be
lost during login with the old library.
This caused "no passkeys found" errors when users tried to login
after registering.
Change residentKey from 'preferred' to 'required' for passkey registration.
Discoverable credentials (resident keys) are required for synced passkeys
like Google Password Manager to properly store and retrieve the passkey.
Also add detailed logging of registration options for debugging.
- Update @simplewebauthn/server from v11 to v13.2.2 for compatibility
- Add debug logging for WebAuthn config and authentication options
- Browser library also needs to be updated to v13.2.2 (manual step)
The version mismatch between server and browser libraries may have been
causing passkey registration/login issues.
The @simplewebauthn/server library defaults to requireUserVerification: true,
but our registration options set userVerification: 'preferred'. This mismatch
caused passkey registration and login to fail with:
"User verification was required, but user could not be verified"
Add requireUserVerification: false to all verification calls to match
the 'preferred' setting in registration/authentication options.
The deleteAccount fetch request sent Content-Type: application/json
but no body, causing Fastify's JSON parser to fail with 400 Bad Request.
Add empty JSON body to match the pattern used by refreshToken.
- Add startup warning in passkey.ts when running with in-memory
challenge storage in production (multi-instance deployments)
- Use LOCK_NAMES.OPERATION_LOG constant in stale-operation-resolver
instead of hardcoded 'sp_op_log' string
- Add client-side validation for empty/whitespace entityId to match
server-side validation and provide earlier feedback
Fixes critical bug where multiple clients with existing data could each
create a SYNC_IMPORT when enabling SuperSync, causing data loss.
Root cause: When clients had local data (from legacy migration), each
would detect "server needs migration" and create competing SYNC_IMPORTs.
The last one uploaded would invalidate all prior data.
Fixes:
- Server rejects duplicate SYNC_IMPORT with SYNC_IMPORT_EXISTS (409)
- Client double-checks server is empty before creating SYNC_IMPORT
- Client merges all local op clocks into SYNC_IMPORT's vector clock
- hasSyncedOps() excludes MIGRATION/RECOVERY ops from check
- Upload service gracefully handles SYNC_IMPORT_EXISTS rejection
Tests added:
- ServerMigrationService: double-check and clock merging (5 tests)
- OperationLogStoreService: hasSyncedOps() MIGRATION exclusion (7 tests)
- OperationLogUploadService: SYNC_IMPORT_EXISTS handling (5 tests)
- E2E: Multiple clients with existing data merge correctly
Authentication changes:
- Add passkey (WebAuthn) as primary login method
- Add email magic link as fallback for devices without passkey support
- Remove password-based authentication entirely
New features:
- Passkey registration and login via @simplewebauthn/server
- Magic link login with 15-minute expiry tokens
- Passkey recovery via email link
- Self-hosted simplewebauthn-browser.min.js for reliability
Database changes:
- Add Passkey model for WebAuthn credentials
- Add PasskeyChallenge model for registration/auth challenges
- Add loginToken and loginTokenExpiresAt fields for magic links
- Add passkeyRecoveryToken fields for passkey recovery
UI changes:
- Login form: email + "Login with Passkey" + "Send Login Link"
- Register form: email + terms checkbox + "Register with Passkey"
- Consistent token display UI for both passkey and magic link login
- Remove password fields and forgot password flow
Security:
- CSP-compliant magic link redirect using external script
- Rate limiting on all auth endpoints
- Single-use magic link tokens
When a SuperSync account is deleted, clients now properly detect
authentication failures and prompt for reconfiguration:
- Add _checkHttpStatus() helper to SuperSyncProvider that throws
AuthFailSPError on 401/403 responses
- Clear cached credentials on auth failure to allow reconfiguration
- Add DELETE /api/test/user/:userId endpoint for E2E testing
- Add deleteTestUser() helper in supersync-helpers.ts
- Add E2E tests for account deletion and reconfiguration scenarios
The existing SyncWrapperService already handles AuthFailSPError by
showing a snackbar with "Configure" action, so no UI changes needed.