Commit graph

21 commits

Author SHA1 Message Date
Johannes Millan
b692eb72ce
feat(rate-dialog): calm, recurring, win-timed store rating prompt (#8704)
* 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>
2026-07-02 13:52:10 +02:00
Johannes Millan
2aacba1c98
fix(ci): repair v18.9.0 release pipeline (MAS build, iOS submit, flaky lock test) (#8040)
* fix(build): keep @electron/asar on minimatch v3 for MAS universal build

The `overrides.app-builder-lib.minimatch` pin to v10 cascaded into
@electron/asar (a child of both app-builder-lib and @electron/universal),
which only works with minimatch v3. minimatch v9/v10 ship as ESM with
`__esModule: true` and no `default` export, so @electron/asar's compiled
default import (`minimatch_1.default(...)`) resolved to undefined and threw
`TypeError: (0 , minimatch_1.default) is not a function` during the
universal-app asar merge (makeUniversalApp -> mergeASARs -> shouldUnpackPath),
failing `dist:mac:mas:buildOnly` in the Mac Store release workflow.

Carve @electron/asar back to minimatch 3.1.2 via nested overrides while
app-builder-lib (and @electron/universal) keep minimatch v10 as intended.

* fix(ci): declare export compliance for iOS App Store submission

The iOS release build, upload and processing all succeed, but
`upload_to_app_store` fails at submit-for-review with:

  [!] Export compliance is required to submit
      Example: submission_information: { export_compliance_uses_encryption: false }

Super Productivity only relies on exempt encryption (HTTPS / standard OS
crypto), so declare that the app does not use non-exempt encryption:

- ios/App/App/Info.plist: ITSAppUsesNonExemptEncryption=false (canonical,
  build-time declaration; also covers TestFlight, auto-resolved by ASC).
- fastlane/Fastfile: export_compliance_uses_encryption: false in
  submission_information, so the submit-for-review API call carries the
  declaration regardless of the binary.

* refactor(build): simplify @electron/asar minimatch override to top-level

Follow-up to the minimatch carve-out. Hoisting a top-level
`overrides["@electron/asar"].minimatch` pin produces a byte-identical
package-lock.json to the previous nested form, but is simpler and covers
every @electron/asar consumer (app-builder-lib, @electron/universal,
electron-winstaller) regardless of which parent wins hoisting — rather than
relying on a nested branch under app-builder-lib (whose @electron/universal
sub-branch was dead config given asar dedupes to a single instance).

* test(op-log): de-flake LockService mutex-invariant timeout test

"should preserve mutex invariant after timeout" flaked on the macOS CI
runner (TZ=America/Los_Angeles leg): it asserted that C always times out
waiting for the lock, but on a slow/loaded runner A can release the lock
before C's 50ms timeout fires, so C legitimately acquires it and runs —
after A has finished. The observed `['a-start','a-end','c-start']` actually
satisfies the invariant (C ran after A, never concurrently); only the
brittle "C must time out" assertion failed.

Assert the real no-concurrent-execution invariant instead: C must never
start before A ends. This still catches the original regression (concurrent
C would push 'c-start' before 'a-end') but is independent of runner timing.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-05 22:26:42 +02:00
Johannes Millan
5aea4b0143
fix(sync): force no-cache on native WebDAV reads (iOS data loss) (#7982)
iOS reads a stale sync-data.json on every WebDAV sync: URLSession's
default configuration caches GET responses, so the client never sees
remote changes and — because the pre-upload conflict check re-reads the
same cached body — blindly overwrites newer remote data. Android (OkHttp,
no cache) and web (fetch `cache: 'no-store'`) are unaffected, which is why
this is iOS-only.

- iOS WebDavHttpPlugin: disable the HTTP cache (urlCache=nil +
  reloadIgnoringLocalCacheData) so sync reads always hit the network.
- WebDavHttpAdapter: send `Cache-Control: no-cache, no-store` + `Pragma`
  on the native request path, covering an upstream proxy/CDN cache too.
- Log allowlisted, non-sensitive cache-relevant response headers so a
  future log capture localises a stale read (client cache vs proxy).

Fixes #7144
2026-06-03 14:46:18 +02:00
johannesjo
bfb9d83a71 build(ios): drop unused remote-notification background mode
The app uses only local notifications — no @capacitor/push-notifications,
no FCM, no registerForRemoteNotifications anywhere. Declaring
remote-notification in UIBackgroundModes claims a push capability the
app never uses, which Apple may flag at App Store review.
2026-05-24 01:23:23 +02:00
Johannes Millan
bbf506fa82 fix(ios): align bundle name with app display name
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-05 15:41:25 +02:00
johannesjo
38e590983c fix(mobile): improve work view dragging and browser pod setup 2026-05-03 22:20:07 +02:00
johannesjo
be6a146529 build: fix ios 2026-03-26 21:23:52 +01:00
johannesjo
926e9d46a8 build: update pdfile and package-lock 2026-03-26 21:06:33 +01:00
Johannes Millan
7b136ecc14 fix(ios): show browser update message on unsupported iOS versions (#6889)
The app targets ES2022+/Safari 18+ but the iOS deployment target was 14.0.
On older iOS versions (e.g., 15), the JavaScript fails to parse, leaving
users stuck on a white splash screen with no feedback.

- Add an inline ES5-compatible browser check in index.html that detects
  unsupported browsers and shows a helpful update message
- Raise iOS deployment target from 14.0 to 16.0 in both project.pbxproj
  and Podfile
- Guard productivity tips script against null elements when compat check
  replaces splash screen content
2026-03-20 21:36:30 +01:00
Johannes Millan
4c4a0a3afd fix(android): use native safe area insets for proper status bar padding
Install capacitor-plugin-safe-area to read native safe area insets and
inject them as CSS variables, working around Capacitor 7's broken
adjustMarginsForEdgeToEdge and Android WebView's unreliable
env(safe-area-inset-*) values.

- Add _initSafeAreaInsets() to GlobalThemeService
- Remove broken adjustMarginsForEdgeToEdge: 'auto' from capacitor config
- Clamp context menu trigger Y to safe area top
- Fix light mode status bar color to match theme (#f8f8f7)
2026-02-11 20:37:40 +01:00
Johannes Millan
3ca70c31bf fix(ios): register WebDavHttpPlugin with Capacitor bridge
The plugin was compiled but never registered on iOS, causing
"WebDavHttp plugin not implemented" errors. Add a custom
CAPBridgeViewController subclass that registers the plugin,
mirroring the Android registration in CapacitorMainActivity.
2026-02-06 17:45:57 +01:00
Johannes Millan
15a12e56c5 fix(ios): make responseHeaders mutable to fix Swift compilation error
The variable was declared as 'let' but mutated via subscript assignment,
causing iOS App Store builds to fail.
2026-02-05 15:59:39 +01:00
Johannes Millan
b66b680e30
fix: address code review findings across sync, tasks, and UI (#6339)
- fix(tasks): preserve selectAllTasks memoization by only filtering when
  undefined entities exist, avoiding new array allocation on every call
- fix(sync): replace mutable ARGON2_PARAMS export with getter/setter to
  prevent test pollution across spec files
- fix(sync): convert tag-task-page from async pipe to toSignal pattern
  to fix OnPush change detection after bulk sync state updates
- fix(sync): remove E2E navigation workarounds that masked the rendering
  bug now fixed by the toSignal conversion
- fix(sync): add clarifying comments for archive-wins sibling conflict
  resolution and waitForSyncWindow switchMap concurrency semantics
- fix(ios): add hex preview of first 16 bytes to WebDAV UTF-8 decode
  error for easier debugging
- docs: add JSDoc to getDiffInWeeks noting negative diff behavior

https://claude.ai/code/session_01Y51QDFEdvrJ9VVgp9XfWLJ

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-03 12:13:57 +01:00
Johannes Millan
740eec68f5 fix(sync): add explicit error handling for UTF-8 decoding in iOS WebDAV plugin
Reject with DECODE_ERROR instead of silently returning empty string
when UTF-8 decoding fails on non-empty response data, preventing
the sync layer from misinterpreting decode failures as empty files.
2026-02-02 17:15:13 +01:00
Johannes Millan
fcedc4ed05 fix(sync): add native iOS WebDAV plugin to fix broken read on iPhone
iOS WebDAV sync could write but not read data due to Capacitor's native
bridge auto-parsing responses as JSON and replacing data with error strings.
Add a native URLSession-based plugin (matching the existing Android one)
that bypasses CapacitorHttp entirely.

Fixes #6317
2026-02-02 17:15:13 +01:00
Johannes Millan
40b18c4693 fix(sync): implement OAuth redirect for Dropbox on mobile
- Add redirect_uri parameter to OAuth flow for mobile platforms
- Create OAuthCallbackHandlerService to handle deep link callbacks
- Register custom URI scheme (com.super-productivity.app://) in Android/iOS
- Add platform-specific UI for OAuth flow (automatic vs manual)
- Implement proper error handling for OAuth callback errors
- Add comprehensive unit tests for callback handler
- Fix memory leak by properly cleaning up event listeners
- Use IS_NATIVE_PLATFORM constant for consistent platform detection

Web/Electron continue using manual code entry (no regression).
Mobile (iOS/Android) now use automatic redirect with deep linking.

Fixes Dropbox OAuth authentication on iOS and Android platforms.
2026-01-21 14:30:24 +01:00
Johannes Millan
f2c1c2ab5e fix(ios): remove white frame from app icon by eliminating alpha channel
iOS renders transparent pixels as white, causing a white frame around the
app icon. This fix generates a 1024x1024 RGB PNG (no alpha channel) from
the existing build/icons/sq2160x2160.png source.

Changes:
- Add tools/generate-ios-icon.js script using Sharp to resize and remove alpha
- Add npm script 'generate:ios-icon' for reproducible icon generation
- Update AppIcon-512@2x.png to RGB format (was RGBA)
- Install Sharp as dev dependency for image processing

Icon is now fully opaque with correct brand blue color and white checkmark.
2026-01-20 17:07:24 +01:00
johannesjo
0177b4860d fix(ios): remove alpha from icon, add safe area styling 2026-01-16 17:25:49 +01:00
johannesjo
9d19a481c8 fix(ios): use correct bundle ID com.super-productivity.app 2026-01-16 16:07:33 +01:00
Johannes Millan
4d17ae8f2f feat(ios): add keyboard and status bar support 2026-01-14 13:45:42 +01:00
Johannes Millan
590e1592da feat(ios): add iOS platform support via Capacitor
Add iOS MVP with core functionality using Capacitor-first approach:

Platform Abstraction Layer:
- Add CapacitorPlatformService for unified platform detection
- Add PlatformCapabilities model with per-platform feature flags
- Add CapacitorNotificationService wrapping LocalNotifications plugin
- Add CapacitorReminderService for cross-platform reminder scheduling

iOS Support:
- Add Capacitor iOS project with proper configuration
- Configure Info.plist for notifications and background modes
- Add app icon and splash screen assets
- Enable CapacitorHttp for WebDAV sync (avoids CORS issues)

Refactoring:
- Update android.effects.ts to use CapacitorReminderService
- Update notify.service.ts to support iOS notifications
- Update startup.service.ts with platform-aware initialization
- Update sync-form.const.ts to hide CORS info on native platforms
- Update webdav-http-adapter.ts to use CapacitorHttp on iOS

MVP includes: task management, scheduled notifications, Dropbox sync,
WebDAV sync, share-out, dark mode. Excludes: background tracking,
widgets, local file sync, share-in (post-MVP features).
2026-01-14 13:45:42 +01:00