- Add defensive undefined guard in sync hydration service
- Improve error messages for auth code exchange failures
- Reset lastServerSeq after backup import to ensure correct sync state
- Clarify revert comment in encryption toggle service
Add missing NEXTCLOUD_DECK case to tooltip and initials helpers,
showing the selected board title prefixed with "Deck:" instead of
the raw provider key.
The pulse circle behind the play button used `animation: pulse 2s infinite`,
which forces the browser compositor to run at 60fps continuously. This caused
~5-10% CPU and ~30% GPU usage while time tracking was active (#6076).
Replace with a finite CSS animation (`animation-iteration-count: 1`) triggered
every 5s via lazySetInterval. The compositor goes fully idle between pulses.
Combined with `will-change: transform` (own GPU layer), the animation does not
cause repaints of surrounding elements.
Closes#6076
- Add selectTaskRepeatCfgsByTagId selector to filter repeat configs
by tagIds
- Show recurring tasks section on both project and tag views
- Paused repeat configs are shown greyed out with a pause icon
- Alphabetical sorting maintained
* fix/test-gpt:
refactor(sync): use discriminated unions for sync orchestrator results
docs(sync): replace completed simplification plan with new roadmap
Add a collapsible "Recurring Tasks" section at the bottom of the
project work-view showing repeatable task configs for the active
project. Clicking a row opens the edit dialog, hovering shows next
due date.
The section is hidden on tag views, when no repeat configs exist
for the project, and when task view filters/grouping are active.
Closes#4933
* fix/issue-4931:
refactor(task-repeat-cfg): deprecate order field and remove from config UI
feat(task-repeat-cfg): add skipOverdue option to recurring tasks
Replace flag-based result objects with DownloadOutcome and UploadOutcome
discriminated unions at the orchestrator level. This eliminates null
returns, optional boolean checks, and ambiguous flag combinations.
- Add DownloadOutcome (5 variants) and UploadOutcome (3 variants) types
- Update OperationLogSyncService to return discriminated unions
- Update SyncWrapperService and ImmediateUploadService consumers
- Use exhaustive switch in downloadCallback adapter
- Replace piggybackedOps array pass-through with piggybackedOpsCount
Add --ensure flag to load-env.js that skips generation if
env.generated.ts already exists, allowing ng serve to work in
fresh worktrees without running npm run env first.
Also make load-env.js idempotent by comparing content before
writing and removing the timestamp from generated output.
Add total, this week, and this month time summaries above the heatmap
in the recurring task config dialog. Uses a pure utility function to
aggregate timeSpentOnDay across all current and archived task instances.
The order field was confusing and not user-friendly. It is removed from
the repeat config dialog and translation keys. The field and its runtime
logic (isAddToBottom, sortRepeatableTaskCfgs) are kept intact for
backwards compatibility with existing configs that have a non-zero order.
order is also removed from SCHEDULE_AFFECTING_FIELDS since it is no
longer editable via the UI.
When enabled on a repeat config, missed/overdue instances (scheduled
dates before today) are silently skipped instead of being created as
overdue tasks. The lastTaskCreationDay watermark is still advanced on
skip to prevent the same past date from being re-evaluated on every
subsequent app open.
Closes#4928, relates to #4931
Differentiate between token exchange errors (HTTP 400 = invalid auth
code) and PKCE generation failures, showing appropriate messages for
each. Previously all auth errors showed a misleading "requires HTTPS"
message.
Also log Dropbox response body on token exchange errors for better
diagnostics.
Closes#6746
- Add handleEncryptionWarningDialog() to supersync-helpers.ts to
eliminate duplicated dialog-handling blocks across import.page.ts
and two supersync spec files
- Replace isVisible() point-in-time check with waitFor({ timeout: 1000 })
in supersync.page.ts to avoid race when dialog is still animating in
- Replace silent .catch(() => {}) with a logged warning when the
encryption dialog fails to close after confirmation
* fix(build): fix tag resolution and add error handling in bump-android-version
The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.
Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.
* fix(sync): differentiate auth error messages and clarify token revocation
- Rename server dashboard "Refresh Token" to "Revoke & Replace Token"
with explicit warning that ALL devices will be disconnected
- Return rejection reason in 401 responses (revoked, expired, etc.)
via discriminated union TokenVerificationResult type
- Show different client error messages for server-side token rejection
vs missing local credentials to aid user diagnosis
- Extract server error reason from JSON response body in AuthFailSPError
Closes#6597
* refactor(sync): use generic auth reason and improve test readability
- Replace 'User not found' and 'Account not verified' with generic
'Account unavailable' to avoid leaking account state in API responses
- Extract long reason strings to constants in middleware.spec.ts
* fix(sync-server): add per-route rate limits to silence CodeQL alert
Add explicit rate limits to routes that only had the global rate limit:
- GET /api/sync/status: 60/min
- DELETE /api/sync/data: 3/15min (destructive operation)
- GET /api/sync/restore-points: 30/min
- GET /api/sync/restore/:serverSeq: 10/5min (CPU-intensive)
- GET /reset-password: 20/15min
- GET /recover-passkey: 10/15min
These complement the global @fastify/rate-limit (100/15min) and silence
the CodeQL 'Missing rate limiting' alert on authenticated handlers.
* fix(build): fix tag resolution and add error handling in bump-android-version
The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.
Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.
* fix(sync): differentiate auth error messages and clarify token revocation
- Rename server dashboard "Refresh Token" to "Revoke & Replace Token"
with explicit warning that ALL devices will be disconnected
- Return rejection reason in 401 responses (revoked, expired, etc.)
via discriminated union TokenVerificationResult type
- Show different client error messages for server-side token rejection
vs missing local credentials to aid user diagnosis
- Extract server error reason from JSON response body in AuthFailSPError
Closes#6597
* refactor(sync): use generic auth reason and improve test readability
- Replace 'User not found' and 'Account not verified' with generic
'Account unavailable' to avoid leaking account state in API responses
- Extract long reason strings to constants in middleware.spec.ts
* fix(sync): reset vector clock to minimal after SYNC_IMPORT
After a SYNC_IMPORT, working clocks were carrying forward ALL accumulated
client IDs (including dead ones from reinstalls/old browsers), keeping the
clock permanently at MAX_VECTOR_CLOCK_SIZE. This caused unnecessary pruning
and comparison issues on every sync.
Three complementary changes:
1. Reset working clock to minimal after SYNC_IMPORT: When a client creates
or receives a SYNC_IMPORT, its working clock is reset to only the import
client's entry + own entry. Dead client IDs are dropped. The full clock
is preserved in the stored SYNC_IMPORT operation for filtering.
2. Import-client-counter exception in SyncImportFilterService: Post-import
ops with minimal clocks appear CONCURRENT with the import (missing old
entries). A new exception recognizes them as post-import by checking if
the op has the import client's counter >= the import's own counter value.
3. Updated SimulatedClient test helper to reset clocks on SYNC_IMPORT,
matching the real behavior in OperationLogStoreService and
SyncHydrationService.
https://claude.ai/code/session_01T5K1kq8m6LPDc6dEGyXnEW
* chore: update package-lock.json
https://claude.ai/code/session_01T5K1kq8m6LPDc6dEGyXnEW
* refactor(sync): use FULL_STATE_OP_TYPES constant and improve test assertions
- Replace magic string casts `(opType as string) === 'REPAIR'` with
`FULL_STATE_OP_TYPES.has(opType)` in simulated-client helper
- Document transitive propagation assumption in import-client-counter
exception comment
- Improve test assertion to use toContain for better failure messages
---------
Co-authored-by: Claude <noreply@anthropic.com>
Suppress noisy "EXITING due to failed single instance lock" message when
a second instance is launched via xdg-open for protocol URL handling.
Register superproductivity:// as a MIME type handler in the .desktop file
so Linux users no longer need manual xdg-settings configuration.
Remove duplicate requestSingleInstanceLock() call in start-app.ts.
Closes#173
- Pre-capture provider config before destructive deleteAndReuploadWithNewEncryption
call so disableEncryption revert restores the original encryption key
- Merge guard check and config pre-capture in enableEncryption to eliminate
redundant getActiveProvider()/privateCfg.load() calls
- Fix isSubTask detection in LWW meta-reducer to use new parentId when present,
correctly handling subtask-to-main-task promotion
- Remove redundant unique() calls guarded by preceding includes() checks
The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.
Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.
The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.
Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.
Ignore entire .claude/ directory to keep local settings and commands
out of version control. Also fix test expectation to match Bug #6726
behavior where pausedTaskId is intentionally undefined on skipBreak.
Remove unnecessary setSelectedId call from goToFocusMode() in the task
context menu. This aligns behavior with other focus mode entry points
(keyboard shortcut, header button) which only set currentTaskId.
Closes#6727
Don't override user's task choice when a break ends or is skipped.
Guard setCurrentTask dispatch with currentTaskId check in skipBreak$
and resumeTrackingOnBreakComplete$ effects, and pass undefined
pausedTaskId when syncTrackingStartToSession$ auto-skips a break.
Remove the 15k-line auto-generated CHANGELOG.md and its conventional-changelog
tooling. The bump-android-version script now derives Play Store fastlane
changelogs from git log between tags instead of parsing CHANGELOG.md.
- Use execFileSync with argv to avoid shell injection
- Filter merge commits with --no-merges
- Handle breaking-change prefix (feat!:) in regex
- Truncate at line boundaries for 500-char Play Store limit
- Remove conventional-changelog-cli and @conventional-changelog/git-client deps
- Remove release.changelog script from package.json
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.
Use JS-computed var(--safe-area-bottom) instead of env(safe-area-inset-bottom)
which returns 0 on Android WebView. Also account for bottom nav height and
position sidebar below status bar on native mobile.
Closes#6561
- Unify duplicated HTTP methods in SuperSyncProvider into shared helpers
- Remove 5 encryption DI injection tokens, use private properties for test spying
- Remove DerivedKeyCacheService wrapper, use direct imports
- Cache _getServerSeqKey() result in SuperSyncProvider
- Split SyncProviderServiceInterface into SyncProviderBase + FileSyncProvider
- Remove dead file-op stubs from SuperSync provider
- Extract shared delete-and-reupload pattern into SnapshotUploadService
- Extract helper methods from operation-log-sync.service.ts
- Auto-invalidate WrappedProviderService cache on config changes
- Fix: preserve auth credentials in encryption toggle revert paths
- Fix: add config revert on disableEncryption failure
~720 lines net reduction across 26 files.
- Add TODAY_TAG to FAKE_ROOT_STATE in TaskArchiveService so
handleUpdateTask can find it when marking tasks as done
- Update dueDay assertion to expect defined value (set to today
by updateDoneOnForTask)
- Add mock tasks to LWW integration tests so orphan filter
doesn't remove taskIds from PROJECT/TAG entities