Replace 36 MB of MP3 files with 4.6 MB of M4A (AAC-LC) files encoded
at 32kbps mono. M4A is required for iOS/Safari compatibility in
Capacitor's WKWebView.
Enable R8 minification and resource shrinking in the Android release
build to further reduce APK size. Add ProGuard rules to keep Capacitor
and OkHttp classes.
Pass followed pubkeys through the Capacitor plugin to the native
polling service. When onlyFollowing is enabled, the relay query
includes an authors filter so only events from followed accounts
trigger native Android notifications.
Disabled notification types (e.g. reactions) still triggered push
notifications and showed the unread dot indicator, even though the
notification tab correctly filtered them out.
Three root causes fixed:
- useHasUnreadNotifications now uses getEnabledNotificationKinds to
only query for enabled types, preventing phantom unread dots
- NotificationSettings now syncs type preference changes to the
nostr-push server via updateSubscription (is_active toggle)
- Native Android poller now receives enabled kinds from the JS layer
and uses them in the relay filter instead of hardcoded kinds
The <a download> and <a target="_blank"> patterns don't work in
WKWebView. Add downloadTextFile() and openUrl() utilities in
src/lib/downloadFile.ts that use @capacitor/filesystem and
@capacitor/share on native platforms, falling back to standard
browser behavior on web.
Update all call sites: onboarding key download (InitialSyncGate,
SignupDialog), image lightbox buttons (ImageGallery, ProfilePage).
Document Capacitor compatibility constraints in AGENTS.md.
Letters were completely absent from the notification pipeline — users had
to visit the Letters page to discover incoming letters. This integrates
kind 8211 into every layer of the notification system:
- useNotifications: query, grouping, and referenced-event exclusion
- useHasUnreadNotifications: unread dot indicator
- NotificationsPage: LetterNotification component with link to /letters
- NotificationSettings: toggleable Letters row
- notificationTemplates: web push template
- Android NotificationRelayService + NostrPoller: native push support
- EncryptedSettings + schema: letters preference field
Closes#188
- Switch from CalVer (date+SHA) to semantic versioning starting at v2.0.0
- Create release skill (.agents/skills/release/) with full AI-guided release workflow
- Add CHANGELOG.md with initial 2.0.0 entry
- Update CI tag regex to match semver tags (v2.0.0 instead of v2026.03.24-sha)
- Extract changelog content into GitLab release descriptions
- Update Android versionName to 2.0.0 in build.gradle
- Update iOS MARKETING_VERSION to 2.0.0 in pbxproj
- Expose VERSION (semver) and BUILD_DATE (ISO 8601) as build-time constants
- Display version and build date in Settings page footer
- Remove npm release script (releases are now done via the AI skill)
When onCreate() catches ForegroundServiceStartNotAllowedException and calls
stopSelf() early, httpClient is never initialized. onDestroy() still runs and
was crashing with a NullPointerException on httpClient.dispatcher().
Android 16 (targetSdk 36) enforces strict time limits on dataSync
foreground services. When the limit is exhausted, calling startForeground()
or startForegroundService() throws ForegroundServiceStartNotAllowedException
and kills the app. Catch the exception in both NotificationRelayService.onCreate()
and MainActivity.onCreate() so the app continues running; the AlarmManager
will reschedule the next fetch cycle at the normal interval.
- Add Android intent-filter with autoVerify for https://ditto.pub/*
- Create DeepLinkHandler component to handle appUrlOpen events at runtime
- Mount DeepLinkHandler in AppRouter inside BrowserRouter context
- Add public/.well-known/assetlinks.json with pub.ditto.app package and signing cert fingerprint
- Add kind 1111 to the Nostr REQ filter in NotificationRelayService
- Add case 1111 to kindToAction() in NostrPoller with the same context-aware
label logic: 'replied to your comment' when k=1111, 'commented on your post' otherwise
The icon generation script was only producing ic_launcher_foreground.png
(adaptive icon layer) and leaving ic_launcher.png/ic_launcher_round.png as
solid purple squares with no logo. This caused the app icon to appear as a
big purple circle on device. The script now generates all three icon types
at all density buckets.
Reverts broken changes from intermediate commits that:
- Changed ic_launcher_foreground.xml viewport from 100x100 to 90x80 with scale
- Changed mipmap-anydpi-v26 to reference @drawable instead of @mipmap foreground
- Changed ic_stat_ditto.xml viewport from 100x100 to 90x80
- Rewrote generate-icons.sh to generate legacy icons with broken viewBox hack
- Produced tiny broken foreground PNGs (318-340 bytes)
All icon resources and the script are now identical to 4abe629, with freshly
regenerated foreground PNGs at the correct 66% content size.
- Regenerate all ic_launcher*.png and ic_launcher_foreground.png which had regressed
to tiny broken files (318-343 bytes) that rendered as white squares on purple
- Fix ic_stat_ditto.xml: change viewportWidth/Height from 100x100 to 90x80 to match
the effective logo bounds, correcting the off-center logo in the notification tray
The logo was still off-center because the SVG viewBox '-5 -10 100 100'
is asymmetric and doesn't properly frame the logo content.
Changes:
- Update generate-icons.sh: Change viewBox from '-5 -10 100 100' to '0 0 90 80'
- Update vector foreground: Change viewportWidth/Height from 100x100 to 90x80
- Adjust pivot point to match new viewport dimensions
- Regenerate all launcher PNGs and adaptive foreground PNGs with corrected viewBox
The logo should now be properly centered without the left gap.
The logo was off-center with a gap on the left due to the SVG's asymmetric
viewBox (-5, -10, 100, 100). Fixed by adjusting rendering and positioning.
Changes:
- Update generate-icons.sh: Render at 600px and crop to 512px for better centering
- Update vector foreground: Shift translateX from 5 to 7 to compensate for viewBox offset
- Regenerate all launcher PNGs and adaptive foreground PNGs
The logo now appears properly centered in the icon.
Zoomed out the logo further from 50% to 40% for better visual balance
and more padding around the logo.
Changes:
- Update generate-icons.sh: Change content_size from 50% to 40%
- Update vector foreground: Scale from 0.75 to 0.6 to match PNG sizing
- Regenerate all launcher PNGs and adaptive foreground PNGs
The logo now has generous padding and looks more refined in the app launcher.
The logo was zoomed in too far, making it difficult to see the full design.
Reduced the logo size to 50% of the icon canvas for proper padding.
Changes:
- Update generate-icons.sh: Change content_size from 66% to 50%
- Update vector foreground: Add 0.75 scale transformation to match PNG sizing
- Regenerate all launcher PNGs with the smaller, better-proportioned logo
- Regenerate all adaptive foreground PNGs
The logo now has proper breathing room and looks much better in the app launcher.
The APK was showing just a purple circle because the legacy launcher PNG files
(ic_launcher.png and ic_launcher_round.png) were solid purple with no logo.
Changes:
- Update generate-icons.sh to generate legacy launcher PNGs (for Android < 8.0)
- Add ImageMagick as fallback SVG renderer when inkscape/rsvg unavailable
- Replace 'bc' calculations with bash arithmetic for better compatibility
- Generate both square and round launcher icons with white logo on purple bg
- Regenerate all launcher icons (mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)
The icons now properly display:
- Purple background (#7c52e0)
- White Ditto logo (66% safe zone sizing)
- Round variant with circular mask
This fixes the issue for both:
- Modern devices (Android 8.0+): Uses adaptive icon system with vector foreground
- Legacy devices (Android < 8.0): Uses pre-rendered PNG launcher icons
- Delete conflicting ic_launcher_background.xml drawable with teal grid pattern
- Update adaptive icon XMLs to use vector drawable instead of PNG mipmap
- Change from @mipmap/ic_launcher_foreground to @drawable/ic_launcher_foreground
- Vector drawable (drawable-v24/ic_launcher_foreground.xml) contains proper white Ditto logo
- Background color (#7c52e0 purple) is correctly defined in values/ic_launcher_background.xml
The APK was showing only a purple circle because the vector foreground logo wasn't being used. Now the adaptive icon system will properly composite the white Ditto logo on the purple background.
- Update strings.xml to use 'Ditto' instead of 'ditto'
- Update capacitor.config.ts appName to 'Ditto'
- Update GitLab CI/CD artifact naming to use 'Ditto-' prefix
- Ensures consistency with web version branding
- Extract zap amount from kind 9735 events in NostrPoller.java (checks
amount tag then falls back to description tag's zap request)
- Format amounts compactly (e.g. 1.5K sats, 2M sats)
- Set data URI on notification PendingIntent so tapping opens /notifications
- Handle deep link intent in MainActivity (both onCreate and onNewIntent)
- Remove dead localNotificationActionPerformed listener from TypeScript
(only fired for Capacitor-API notifications, not natively posted ones)
The notification service was holding a PARTIAL_WAKE_LOCK indefinitely,
preventing the CPU from ever sleeping. Combined with OkHttp's 30-second
ping interval, this caused continuous CPU wake-ups — worse than the
previous polling approach.
Changes:
- Remove permanent WakeLock that kept CPU running 24/7
- Disable OkHttp's 30s ping interval (useless without WakeLock in Doze)
- Add AlarmManager keepalive using setAndAllowWhileIdle() every ~8 min
- Acquire brief WakeLock (15s timeout) only during keepalive checks
- Track connection liveness via relay message timestamps
- Reconnect automatically when Doze kills the TCP connection
- Missed events are recovered via the existing 'since' timestamp
Expected improvement: ~3 brief wake-ups/hour vs continuous CPU usage.
Notifications may be delayed up to ~8 min in deep sleep.
Android 12+ crashes the app if startForeground() isn't called within 5
seconds of startForegroundService(). Moved it from onStartCommand() to
the top of onCreate() to ensure the foreground notification is posted
before any other initialization.
Replaces the AlarmManager-based polling (wake every 60s, open/close WebSocket
connections) with a persistent WebSocket subscription to a Nostr relay. Events
are delivered in real-time via the open connection. A partial WakeLock keeps
the connection alive through Doze mode (same approach as Signal without FCM).
- Add NotificationRelayService: foreground service with persistent WebSocket,
exponential backoff reconnection, network-aware reconnect, relay rotation
on failure, and SharedPreferences config listener
- Refactor NostrPoller into notification dispatcher with metadata caching
(SharedPreferences-based, avoids redundant WebSocket connections for
repeat authors)
- Remove NotificationService (AlarmManager scheduler) and PollAlarmReceiver
(WakeLock-based polling)
- Remove SCHEDULE_EXACT_ALARM permission (no longer needed)
- Disable web-layer background refetch on native (persistent connection
handles it)