* feat(rate-dialog): native store review + prompt after a productive win
Baseline of PR #8680 (squashed) so review improvements land as a separate,
cherry-pickable commit on top.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(rate-dialog): calm banner, feedback cooldown, delayed win prompt
Review + UX follow-ups on top of PR #8680:
- Fix (correctness): re-check full eligibility at prompt fire-time, not just
opt-out, so a crash/data-damage recorded after arming still suppresses it.
- Fix: iOS advances the rating cadence only once the native request resolves;
a reject leaves eligibility intact instead of burning a lifetime prompt.
- Fix: Android review-flow failure now logs and abandons instead of opening the
Play Store unprompted (the trigger is an automatic win, not a user tap).
- UX: web/electron/F-Droid now show a calm, non-modal banner that opens the full
rate/feedback dialog on request, rather than a modal shoved in mid-flow.
- UX: 'give feedback' no longer permanently opts out — it starts a long cooldown
(~90 app-start days) so an engaged user can still be asked once more later.
- UX: the win prompt fires a few seconds after the completion, not on the tap.
- UX: dedicated 'Send feedback' entry in the Help menu (GitHub Discussions).
- UX: show the maintainer email as selectable text (mailto: dead-end fallback).
- Refactor: move selectTodayProgress into work-context.selectors (colocation);
idempotency guard on the win subscription.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(rate-dialog): import WIN_PROMPT_DELAY_MS in spec instead of mirroring it
Export the delay constant from the service and reference it in the spec so the
test can't silently drift from the real value. Found by a review pass.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(rate-dialog): recurring cadence, version-age gate, GitHub star CTA
Growth-focused follow-ups (review found the lifetime cap starves review
recency/velocity, which is what stores rank on):
- Recurring cadence: after the fixed onboarding tiers (32/96 app-start days) the
prompt no longer stops forever — it recurs every ~180 app-start days (~6+
months of real use), well inside Apple's ~3/365 allowance and Play's own
quota. Still calm: win-timed, opt-out/crash/feedback-gated, OS-throttled.
- Version-age gate: hold the prompt for 7 days after the app version changes so a
fresh (possibly regressed) release isn't asked to be rated immediately. Tracked
via two device-local LS keys, checked at arm time and fire time.
- Play tier-burn is now only a deferral, not a lifetime loss (recurring cadence).
- Web/Electron CTA: 'Star us on GitHub' (the desktop-distribution equivalent of
store ranking) instead of the near-zero-conversion how-to-rate doc.
Tests cover recurrence at + beyond the last tier, the version-age gate, and the
updated cadence expectations. 60/60 green; tsc clean.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(rate-dialog): register iOS plugin, drop version-age gate, cut per-tick churn
Second multi-review pass (7 reviewers):
- CRITICAL (Codex, verified): the iOS StoreReview Capacitor plugin was never
registered — CustomViewController registers WebDavHttpPlugin but not
StoreReviewPlugin, so requestReview() rejected and the native App Store review
card never showed. Register it via registerPluginInstance.
- Remove the version-age gate (4 reviewers): getAppVersionStr() changes every
release and the app ships ~weekly, so the 7-day window kept re-arming on every
update (web SW reload / Electron auto-update) and near-permanently suppressed
the prompt on desktop — the platform where the GitHub-star intent matters most.
It also duplicated the 30-day crash gate, which already covers crashing
regressions. Drops 2 LS keys, a constant, and ~40 LOC.
- Perf: add distinctUntilChanged on the armed win stream so the 1s time-tracking
tick (new {done,total} with identical numbers) no longer re-runs scan/filter
every second for the whole session.
59/59 specs green; tsc clean. Kotlin/Swift can't be compiled here — the iOS
registration needs an on-device/simulator smoke test.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(i18n): translate new rate-prompt strings into all locales
Add BANNER_ACTION, BTN_STAR_GITHUB (F.D_RATE) and SEND_FEEDBACK (MH.HM) to all
27 non-en locale files — previously they existed only in en.json and fell back
to English for everyone else. SEND_FEEDBACK reuses each locale's existing
feedback wording for consistency; GitHub is kept untranslated.
Deliberately edits locale files beyond the usual en-only workflow, per request.
Best-effort translations — a native check on the less-common languages is
welcome.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* test(e2e): update Show/hide task panel button label
The TOGGLE_DETAIL_PANEL translation was renamed from "Show/Hide
additional info" to "Show/hide task panel" in dd789d2, but e2e
selectors still referenced the old string, causing every test that
opens the task detail panel to time out.
https://claude.ai/code/session_011mX4Pr24S42svmA5j7fRux
* 18.2.1
---------
Co-authored-by: Claude <noreply@anthropic.com>
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.
* 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>
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