Commit graph

322 commits

Author SHA1 Message Date
Johannes Millan
1f22fe685d 18.1.3 2026-04-01 21:27:44 +02:00
Johannes Millan
3a216b88e1 18.1.2 2026-04-01 21:25:48 +02:00
Johannes Millan
3e0f26aa5d 18.1.1 2026-04-01 21:17:20 +02:00
Johannes Millan
d32f7037a3 fix(sync): preserve own vector clock counter across full-state op resets
When a full-state op (SYNC_IMPORT/BACKUP_IMPORT) arrived from another
client, mergeRemoteOpClocks reset the local clock to a "minimal" form
but only preserved the current client's counter from the incoming op's
clock. If the current client had issued ops (e.g. GLOBAL_CONFIG) not
reflected in the incoming full-state op's clock, its counter was dropped,
causing subsequent ops to reuse the same counter value. Downstream clients
then saw these ops as EQUAL (duplicate) and skipped them silently.

Fix: take max(mergedClock[clientId], currentClock[clientId]) when
rebuilding the clock after a full-state op reset.

Also add __SP_E2E_BLOCK_WS_DOWNLOAD flag to WsTriggeredDownloadService
to allow E2E tests to block automatic WS-triggered downloads during
concurrent conflict scenarios.

Fix archive conflict test by blocking WS downloads on Client A during
the concurrent edit phase so it doesn't auto-receive B's rename via
WebSocket before archiving (restoring the intended conflict scenario).

Fix LWW singleton test to assert convergence rather than specific winner.
Fix renameTask helper to avoid Playwright/Angular re-render races.
Fix shepherd.js import paths that broke the Angular dev server build.
2026-04-01 15:41:13 +02:00
Johannes Millan
db3eaa6f81 18.1.2 2026-03-31 22:31:59 +02:00
Johannes Millan
49f63e8174 18.1.1 2026-03-31 22:31:40 +02:00
Johannes Millan
edb102534e
feat(android): improve task title from share intent using EXTRA_SUBJECT (#7052)
* feat(android): improve task title from share intent using EXTRA_SUBJECT

Browsers and many apps send the page title via Intent.EXTRA_SUBJECT, which
was previously ignored. Now the share handler uses subject > title > content
to build a meaningful task title instead of the generic "Check: Shared Content".
For links without metadata, a readable URL (domain + path) is used instead.

https://claude.ai/code/session_01LzFepWFjLKQSWKZn4iXEpS

* chore: update package-lock.json after npm install

https://claude.ai/code/session_01LzFepWFjLKQSWKZn4iXEpS

* refactor(android): simplify _buildTaskTitle and fix double spaces in URL

Deduplicate the subject/title preference logic and collapse multiple
spaces in _readableUrl output caused by leading slashes in pathnames.

https://claude.ai/code/session_01LzFepWFjLKQSWKZn4iXEpS

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 13:12:34 +02:00
Johannes Millan
85a8426213 18.1.0 2026-03-29 20:31:02 +02:00
Johannes Millan
136bb51ea0 fix(android): prevent crashes from null bridge and foreground service contract violations
Guard against null bridge/webView after onCreate by showing WebViewBlockActivity
with actionable guidance instead of crashing. Satisfy Android's foreground service
contract in TrackingForegroundService and FocusModeForegroundService by posting a
minimal notification before stopping when started without a valid action. Handle
null taskId on ACTION_START without killing an active tracking session.
2026-03-29 19:57:10 +02:00
Johannes Millan
9f8cf0a3d0 fix(oauth): use platform-specific redirect URIs and iOS client ID
Google rejects custom scheme redirect URIs that don't match the
platform's app identifier. Use the Android applicationId
(com.superproductivity.superproductivity) on Android and the iOS
bundle ID (com.super-productivity.app) on iOS, with single-slash
URI format per Google's documentation.

- Add iosClientId to OAuthFlowConfig and plugin API types
- Add iOS OAuth client ID to Google Calendar plugin
- Select client ID per platform (Android/iOS/Desktop) in bridge service
- Add Android package name intent filter in AndroidManifest
- Accept both URI schemes in OAuth callback handler
- Fix redirect handler to use IS_NATIVE_PLATFORM consistently
2026-03-27 17:33:38 +01:00
johannesjo
4826bc265c 18.0.2 2026-03-26 21:24:24 +01:00
johannesjo
25e31656d1 18.0.1 2026-03-26 21:09:48 +01:00
Johannes Millan
4eca106653 18.0.0 2026-03-26 19:58:29 +01:00
Johannes Millan
142107b12e build: update capacitor and package-lock.json 2026-03-26 17:43:04 +01:00
Johannes Millan
fe0edaacd0 18.0.0-rc.0 2026-03-24 21:04:33 +01:00
Johannes Millan
bbf3c741a7 feat(tasks): lock drag-and-drop Y-axis on small screens and throttle tree drag
Lock cdkDragLockAxis to Y on small screens (<600px) for task-list and
planner components, matching the breakpoint where planner switches to
single-column and bottom nav appears. Nav menu tree always locks Y.
Uses reactive LayoutService.isXs() so orientation changes work correctly.

Throttle tree-dnd onDragMoved with requestAnimationFrame to reduce
layout thrashing from getBoundingClientRect/elementFromPoint calls.
2026-03-24 16:25:42 +01:00
Johannes Millan
f20ebea583 fix(android): prevent ForegroundServiceDidNotStartInTimeException and bridge NPE
Add safeStopSelf() to TrackingForegroundService and FocusModeForegroundService
that calls startForeground() before stopSelf() in fallback branches (missing
task ID, stop when not running, null/unknown action). Android requires
startForeground() within 10s of startForegroundService().

Replace ambiguous NullPointerException in CapacitorMainActivity.onCreate with
a descriptive IllegalStateException when Capacitor bridge fails to initialize.
Add safe-call operators in onSaveInstanceState/onRestoreInstanceState.
2026-03-24 16:25:42 +01:00
Johannes Millan
02bc3e88e3 feat(two-way-sync): add plugin OAuth, two-way field sync, and remote issue deletion
Add OAuth support for plugins with PKCE, token persistence in local-only
IndexedDB, and Electron/web redirect handling. Extend two-way sync with
dueDay, dueWithTime, and timeEstimate field mappings including mutually
exclusive field clearing. Support remote issue deletion on task delete
and remote deletion detection during polling.

Add Google Calendar plugin (disabled from bundled builds pending legal
review) as a development reference for the plugin OAuth and two-way
sync APIs.

Additional fixes:
- Restore accidentally deleted focus-mode translation keys
- Remove full Task[] from deleteTasks action to prevent op-log bloat
- Add dueDay/deadlineDay format validation guards (#6908)
- Fix Android reminder alarm cancel intent matching
- Validate dueDay format to prevent false overdue from corrupted data
- Fix PKCE test expectation after utility consolidation
2026-03-22 13:02:41 +01:00
Johannes Millan
da41ad7603 fix(android): match cancel intent action to schedule intent for reminder alarms
cancelReminder() created a PendingIntent without setting the action,
while scheduleReminder() set ACTION_SHOW_REMINDER. Android's
Intent.filterEquals() treats null != "action" so alarmManager.cancel()
silently failed to find the scheduled alarm.
2026-03-22 10:10:19 +01:00
Johannes Millan
c13bf03d37
fix(android): prevent stale check from suppressing valid reminder notifications (#6905)
* fix(android): prevent stale check from suppressing valid reminder notifications

The isTaskStale check in ReminderAlarmReceiver was incorrectly suppressing
all locally-scheduled reminders. After 6fd0696 enhanced extractRemindersToSchedule
to detect schedule ops from actionPayload, the stale check would find the
task's own schedule op and mark it as stale — even though the alarm IS the
current schedule.

Fix by passing triggerAtMs through the alarm intent and comparing it with
schedule ops' remindAt. A task is only considered rescheduled (stale) when
the schedule op's remindAt differs from the firing alarm's trigger time.

https://claude.ai/code/session_013216HfgtetW9KKzFr5ieLQ

* fix(android): handle dual-reminder edge case and seq cursor reset

Two additional fixes found during review:

1. Dual-reminder edge case: A task can have both a standard reminder
   (remindAt) and a deadline reminder (deadlineRemindAt) with different
   times. The stale check now matches on isDueDate to prevent one
   reminder type from suppressing the other.

2. Seq cursor stuck after server reset: If the server is wiped (e.g.,
   clean sync import), latestSeq drops below the stored lastSeq. The
   worker now resets the cursor when the server's seq is lower, instead
   of being permanently stuck.

https://claude.ai/code/session_013216HfgtetW9KKzFr5ieLQ

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-21 01:08:44 +01:00
Johannes Millan
6fd069658e fix(android): detect schedule/unschedule ops in background sync reminder worker
The op-log system returns empty entityChanges for most actions — only
TIME_TRACKING and addTask (CRT) ops populate it. The background sync
provider was relying on entityChanges to detect remindAt changes, so
schedule/unschedule/deadline actions were invisible to the worker.

Fix by matching on actionType strings and parsing actionPayload directly:
- unscheduleTask, removeDeadline → cancel reminders
- scheduleTaskWithTime, reScheduleTaskWithTime → parse actionPayload.remindAt
- setDeadline → parse actionPayload.deadlineRemindAt
2026-03-20 21:36:31 +01:00
Johannes Millan
a87e9ea7a9 fix(android): fix background sync reminder ordering and title extraction
Let later ops win when dismiss and reschedule are in the same batch
(previously cancellation always won, suppressing rescheduled reminders).
Also extract task title from actionPayload as fallback for UPD ops.
2026-03-20 21:36:30 +01:00
Johannes Millan
6d3006dd28 fix(android): use full server format for background sync op parsing
The SuperSync server returns operations in full format (full field names
and NgRx action strings), but the parser was using compact format codes.
This caused all background sync parsing to silently fail, preventing
reminder cancellation and scheduling.
2026-03-20 21:36:30 +01:00
Johannes Millan
167bdedec1 feat(android): check-before-show and schedule reminders from background sync
Add server-side stale check to ReminderAlarmReceiver before showing
notifications. When an alarm fires, the receiver does a quick server
fetch (5s OkHttp timeout, fail-open) to suppress notifications for
tasks that were deleted, done, dismissed, or rescheduled on another
device.

Also extend the background sync worker to schedule new/updated
reminders discovered in sync operations, not just cancel stale ones.

Key design decisions:
- Fail-open: any error or timeout still shows the notification
- Receiver only checks its own task; Worker owns all other reminder
  management and the seq cursor
- Last-writer-wins dedup via Map keyed by (taskId, isDueDate)
- Cancellation wins over scheduling in the same batch
- Independent try/catch per cancel/schedule to prevent cascading failures
- SupervisorJob on receiver's CoroutineScope
2026-03-20 21:36:30 +01:00
Johannes Millan
eb72992d3e fix(android): delay debug toast by 10 seconds 2026-03-20 21:36:30 +01:00
Johannes Millan
10a3db97ea
feat(droid): background sync notifications for android
* docs: add plan for Android background sync via WorkManager

Outlines the implementation plan for using WorkManager to periodically
sync with SuperSync server in the background, cancelling stale reminders
for tasks that were completed/deleted on other devices.

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* docs: refine plan based on multi-agent architecture review

- Pin frontend hook to SyncProviderManager.currentProviderPrivateCfg$
- Add skipWhileApplyingRemoteOps() guard for selector-based effect
- Add HRX (dismiss reminder) and deadlineRemindAt to parser
- Add batch operation ds field handling
- Add ProGuard rules step for release builds
- Add error handling note for BootReceiver WorkManager enqueue

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* docs: improve plan after second review round

Key changes:
- Remove Step 5 (ReminderAlarmStore lookup) - hash-based cancellation is
  sufficient, no store lookup needed
- Add BackgroundSyncProvider interface for extensibility to Dropbox/WebDAV
- Per-account lastServerSeq keyed by baseUrl hash
- Add SyncReminderScheduler helper to deduplicate scheduling logic
- Document edge cases: account switching, token expiry, force-kill,
  gapDetected, notification race conditions
- Add extensibility section explaining Dropbox feasibility and architecture

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* feat(android): add background sync to cancel stale reminders via WorkManager

When a task is completed/deleted on desktop, Android reminders for that task
would still fire because the mobile app didn't know about the change. This adds
a WorkManager periodic job (every 15 min) that fetches operations from the
SuperSync server and cancels AlarmManager alarms + notifications for tasks that
are done, deleted, archived, or had their reminders dismissed.

Architecture:
- BackgroundSyncProvider interface for extensibility (Dropbox/WebDAV possible later)
- SuperSyncBackgroundProvider: lightweight HTTP client + operation parser
- SyncReminderWorker: CoroutineWorker that fetches ops and cancels reminders
- SyncReminderScheduler: helper to schedule/cancel the WorkManager job
- AndroidSyncBridgeEffects: mirrors SuperSync credentials to native SharedPreferences
- Per-account lastServerSeq prevents account-switching bugs

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* chore: update package-lock.json after npm install

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* fix(android): address review issues and add tests for background sync

Fixes from code review:
- Fix distinctUntilChanged to properly handle provider switching
  (non-SuperSync emissions now treated as equal to prevent repeated clears)
- Add null-safe ops array parsing in SuperSyncBackgroundProvider
- Add write/call timeouts to OkHttpClient (30s write, 60s call)
- Remove no-op `hash and hash` line in Kotlin hash function

Tests:
- Add cross-platform parity tests for notification ID hash function
  (pinned expected values that must match Kotlin implementation)
- Add AndroidSyncBridgeEffects unit tests covering:
  - distinctUntilChanged comparator behavior
  - credential set/clear decision logic
  - observable filtering integration (dedup, null filtering)

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* fix(android): fix hash overflow and operation parser payload paths

Two critical bugs found during final review:

1. Hash function abs(Int.MIN_VALUE) bug: In Kotlin, abs(Int.MIN_VALUE)
   returns Int.MIN_VALUE (still negative) due to 32-bit overflow.
   Fixed by converting to Long before abs(), matching JS behavior where
   Math.abs works on 64-bit floats.

2. Operation parser payload structure: The parser was looking at
   p.isDone and p.task.changes directly, but the actual compact
   operation format wraps everything in p.entityChanges[] and
   p.actionPayload. Rewrote to use p.entityChanges as primary source
   (consistent structure with entityType, entityId, changes fields)
   and p.actionPayload.task.changes as secondary source.

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* docs: add long-term plan for Android background sync improvements

Covers three phases: fast app startup via cached sync seq,
push-based notification cancellation via FCM, and extending
background sync to Dropbox/WebDAV providers.

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

* fix(android): address review feedback for background reminder sync

- Remove PLAN.md from repo root (C1)
- Use EncryptedSharedPreferences for access token storage with
  fallback to standard SharedPreferences on broken KeyStore (C2)
- Reset lastServerSeq when access token changes to prevent stale
  seq on account switch with same server URL (I1)
- Add max iteration guard (100) to pagination loop to prevent
  infinite loops from server bugs (I2)
- Use type predicate filter instead of non-null assertions (I3)
- Add exponential backoff (5min) to WorkManager schedule (S1)
- Add comments linking action type codes to frontend source (S2)
- Extract distinctUntilChanged comparator to named function with
  JSDoc explaining the suppression semantics (S3)

https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-20 10:24:02 +01:00
Johannes Millan
525b5184bb 17.5.2 2026-03-19 18:59:20 +01:00
Johannes Millan
3f81cc8852 17.5.1 2026-03-19 15:23:42 +01:00
Johannes Millan
fd5d8ef306 17.5.0 2026-03-19 14:54:32 +01:00
Johannes Millan
793285614a 17.4.0 2026-03-19 14:43:06 +01:00
Johannes Millan
06b8831948
fix(android): match navigation bar color to system theme on startup (#6872)
Add values-night/colors.xml with dark theme colors (#131314) so the
navigation bar and status bar match the system dark theme before the
Angular app boots. Also fix light-mode status bar initial color to
#f8f8f7 to match what the app sets dynamically.

https://claude.ai/code/session_017CVEn9yTUqcPiRzoafP24R

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 01:43:14 +01:00
Johannes Millan
945b8ed15c fix(android): improve startup overlay to webapp add task bar transition
- Match webapp styling: replace colored border stroke with 12dp
  elevation shadow matching the webapp's global bar variant
- Show webapp add task bar when native overlay bar is open but empty,
  not just when it contains text
- Target global add task bar specifically (add-task-bar.global) to
  avoid focusing an inline bar that may already exist on the page
2026-03-18 13:14:10 +01:00
Johannes Millan
49952c5c6a feat(android): add native startup overlay for quick task entry
Show a native FAB button during app startup so users can add tasks
while Angular bootstraps. Tapping the FAB expands a themed input bar
with keyboard tracking. Submitted tasks queue in SharedPreferences
via WidgetTaskQueue and are processed by AndroidEffects after
hydration. Partial (unsubmitted) text transfers to the web AddTaskBar
via sessionStorage.

Design:
- Two-phase dismiss keeps native overlay visible until web input mounts
- MutationObserver with resolved guard detects AddTaskBar readiness
- Day/night theme support with primary-colored border
- FAB color (#6495ED) matches the loading spinner
- Overlay cleaned up in onDestroy to prevent listener leaks

Also removes the old Quick Add Widget (QuickAddActivity,
QuickAddWidgetProvider, and all associated resources).

Also modernizes the loading spinner from double-bounce to a subtle
pulse animation.
2026-03-18 13:14:10 +01:00
Johannes Millan
8a4473541e fix(android): set navigation bar color to match app theme
Add NavigationBarPlugin Capacitor plugin to dynamically update the
Android system navigation bar color and icon style when the app theme
changes. Set initial color in native XML styles for all themes
including the launch theme. Reduce bottom nav and FAB shadows to
prevent visual cutoff at the system navigation bar boundary.
2026-03-18 13:14:10 +01:00
Johannes Millan
42dc92b10d fix(sync): prevent empty-upload to WebDAV on Android
Add defense-in-depth guards to prevent the Capacitor bridge from
silently uploading zero-byte files to WebDAV, which corrupts sync
state for all clients.

- Add empty-data guard in WebdavApi.upload() with tests
- Reject empty PUT body in Android WebDavHttpPlugin (Kotlin)
- Validate compressAndEncryptData() output for all providers
- Add payload size diagnostic logging in WebDavHttpAdapter
- Use android.util.Log instead of println for proper logcat filtering

Closes #6855
2026-03-17 13:59:40 +01:00
Johannes Millan
d401e6169e feat(tasks): add deadlines feature with reminders and planner integration
Add deadline support for tasks with date-only and time-specific deadlines.

Core:
- Add deadlineDay, deadlineWithTime, deadlineRemindAt fields to task model
- Add NgRx actions (setDeadline, removeDeadline, clearDeadlineReminder)
- Add meta-reducer with mutual exclusivity and input validation
- Register deadline actions in ActionType enum and op-log codes

UI:
- Create deadline dialog with calendar, time input, and reminder options
- Add deadline badge to task list row with overdue warning color
- Add deadline item to task detail panel with flag icon
- Add deadline options to task context menu
- Show deadlines in scheduled list page

Reminders:
- Add deadline reminder effects and selectors
- Show reminder dialog with grouped deadline/schedule sections
- Handle deadline reminder clearing on dismiss, done, add-to-today
- Cancel Android native deadline notifications on delete/archive

Planner:
- Show deadline tasks in planner day view and overdue section
- Create dedicated planner-deadline-task component
- Optimize deadline grouping with O(N) single-pass selector

Other:
- Add deadline-today banner effect for unplanned deadline tasks
- Add translation keys for all deadline UI strings
- Add E2E tests for deadline reminder flows
- Add unit tests for deadline reducer and overdue utility
- Extract shared isDeadlineOverdue utility function
- Guard app-state selectors against undefined during teardown
2026-03-15 18:48:00 +01:00
Johannes Millan
591fc42483 17.4.1 2026-03-13 19:54:39 +01:00
Johannes Millan
f410aff077 17.4.0 2026-03-13 16:41:54 +01:00
Johannes Millan
6768eaeb22 fix(android): show correct notification text for due date reminders
The notification body incorrectly showed "Note reminder" for all
non-TASK reminder types, including DUE_DATE reminders. Replace the
if/else with a when expression that handles each type explicitly.
2026-03-10 15:58:22 +01:00
Johannes Millan
b0335c6ee5 17.3.0 2026-03-07 13:28:20 +01:00
Johannes Millan
fd28c04cce feat(reminder): overhaul mobile notification UX for Android and iOS
- Add notification actions: Done, Snooze 10m, Snooze 1h (Android + iOS)
- Add BootReceiver to re-register alarms after device reboot
- Add SharedPreferences queues for background-to-frontend communication
  (ReminderDoneQueue, ReminderSnoozeQueue, ReminderTapQueue)
- Add "fire and verify" pattern: sync before acting on stale notifications
- Add due-date notification scheduling (configurable hour, default 9 AM)
- Add exact alarm permission warning for Android 12+
- Add notification grouping (Android groups, iOS thread identifiers)
- Add isOngoing flag for persistent alarm-style notifications
- Sanitize REMINDER_TASK_ID to prevent JS injection
2026-03-06 21:19:55 +01:00
Johannes Millan
ed92ea7933 17.2.4 2026-03-01 00:19:19 +01:00
Johannes Millan
a87bff8661 17.2.3 2026-02-28 21:52:29 +01:00
Johannes Millan
9372a14f4f 17.2.2 2026-02-28 21:41:25 +01:00
Johannes Millan
f44e6b54cf 17.2.1 2026-02-21 15:15:34 +01:00
Johannes Millan
b79fa5ac29 17.2.0 2026-02-21 13:49:15 +01:00
Johannes Millan
de03a2d11e fix(android): prevent crash on SAF folder selection (#6545)
Wrap takePersistableUriPermission() in try/catch to handle SecurityException
thrown on certain Samsung/Android 13 devices. Reject the Capacitor call on
failure so the error propagates cleanly. Add .catch() to FormlyBtnComponent
promise chain to prevent unhandled rejections.
2026-02-16 18:44:27 +01:00
Johannes Millan
f9fff40402 17.1.8 2026-02-15 11:20:46 +01:00
Johannes Millan
f3eb92c617 17.1.7 2026-02-13 16:16:25 +01:00
Johannes Millan
02fe1a5ff2 fix(android): persist share intent to SharedPreferences for cold-start reliability
Share-to-app via Android SEND intent was unreliable on cold start because
share data was stored only in memory (pendingShareIntent). If the process
died before Angular loaded, the data was lost. Now share data is persisted
to SharedPreferences immediately and pulled synchronously by the frontend.
2026-02-13 14:06:43 +01:00