Replace the transitional Phase 2B-1 comments (which referenced a temporary
AppDelegate registration safety net) with a concise description of the
plugin registration now that it is finalized. Comment-only change; plugin
registration behavior is unchanged.
Remove the getShareExtensionDiagnostics() API now that the share-target
investigation is complete and it has no remaining callers.
- Drop getShareExtensionDiagnostics from the plugin definition, web stub,
Swift plugin method + registration, and SharedImageUtility implementation
- Remove the ShareExtensionDiagnostics TypeScript interface and its now-dead
import in the web stub
- Remove the diagnostics-only [ShareTarget] log line and the now-unused
shareExtensionLastStartKey constant in SharedImageUtility
getSharedImage/hasSharedImage, SharedPhotoReady logic, ShareViewController,
AppDelegate, and Android are unchanged. TypeScript type-check passes.
Remove the temporary diagnostics added during the iOS share-target
investigation, tagged with the TEMPORARY SHARE TARGET DIAGNOSTICS marker.
- Drop the pendingShareExists field/computation from ShareExtensionDiagnostics
across the Swift utility, TS definitions, and web stub
- Remove the cold-start diagnostics block in main.capacitor.ts (the iOS
getShareExtensionDiagnostics call and [ShareTarget] Cold-start state dump)
- Drop pendingShareExists from the getShareExtensionDiagnostics log line
Kept getShareExtensionDiagnostics itself (unmarked plugin API + registration),
the unmarked [ShareTarget] print logging, share-target functionality, and
Android code unchanged.
Remove the native trace infrastructure that was added solely for the iOS
share-target investigation. This reverts the temporary share-extension and
app-launch trace logging along with the APIs and debug UI built on top of it.
- Remove getShareExtensionTrace/clearShareExtensionTrace/getAppLaunchTrace/
clearAppLaunchTrace from the native plugin, TS definitions, and web stubs
- Remove appendTrace (ShareViewController) and appendAppLaunchTrace
(SharedImageUtility) implementations and all call sites
- Drop the share-extension-trace.log / app-launch-trace.log file handling
- Delete ShareTargetDebugView, its /share-target-debug route, and the
TestView entry point
- Remove now-unused AppDelegate isTimesafariURL helper and launchURL local
- Strip trace dump/alert logic from main.capacitor.ts
Share-target behavior, plugin registration, image storage, SharedPhotoReady
logic, Android, and the share-extension diagnostics snapshot are unchanged.
Now that AppBridgeViewController registers SharedImagePlugin from
capacitorDidLoad(), remove the obsolete plugin-registration logic from
AppDelegate: the 5-attempt retry loop in didFinishLaunchingWithOptions,
the registerSharedImagePlugin() method, and the registration-specific
logging. SharedImagePlugin is now registered from a single, deterministic
site.
All non-registration responsibilities are unchanged: notification
handling, URL and universal-link proxying, SharedPhotoReady activation
logic, every lifecycle callback, the temporary app launch tracing, and
all temporary share-target diagnostics remain intact. No JS or Android
changes.
Introduce AppBridgeViewController, a CAPBridgeViewController subclass that
registers the app-local SharedImagePlugin from capacitorDidLoad(), where
the Capacitor bridge is guaranteed to exist. This is the first step toward
replacing AppDelegate's timed retry registration with a deterministic,
bridge-ready registration point.
Point the existing root bridge controller in Main.storyboard at
AppBridgeViewController (same VC id/scene; only the custom class changes)
and add the new file to the App target in project.pbxproj.
The existing AppDelegate registration and its retry loop are intentionally
left in place as a temporary safety net, so the plugin is now registered
from both sites during this phase. No diagnostics, retry logic, JS, or
Android code changed; plugin name ("SharedImage") and implementation are
unchanged.
Add temporary, append-only tracing of the native iOS application launch
lifecycle to diagnose failed cold-start shares when Xcode is not
attached during launch. Writes app-launch-trace.log in the same App
Group container as share-extension-trace.log, reusing the same style
(ISO8601 timestamps, append-only, all logging failures swallowed).
AppDelegate now traces every lifecycle callback
(didFinishLaunchingWithOptions, application(open:),
applicationDidBecomeActive, applicationWill/DidEnterForeground/Background,
applicationWillResignActive, applicationWillTerminate, and
checkForSharedImageOnActivation), recording the callback name, whether a
URL was supplied, the URL value, whether it matches timesafari://,
whether launch options carried a URL, and whether shared-image
activation ran.
Expose read-only getAppLaunchTrace()/clearAppLaunchTrace() plugin APIs
(mirroring the share-extension trace APIs) with matching TypeScript
definitions and web stubs. main.capacitor.ts logs the full launch trace
between APP LAUNCH TRACE START/END markers inside the existing
diagnostics block.
No share-target behavior changed; Android untouched. All additions are
marked TEMPORARY SHARE TARGET DIAGNOSTICS.
Eliminate the iOS startup race between asynchronous SharedImage plugin
registration and the first shared-image check. Previously the initial
check fired on a fixed 1000ms timer that only assumed the native plugin
(registered by AppDelegate with retries from T+500ms) was ready, so a
slow registration could make the first getSharedImage() call throw and
miss a cold-start share.
Replace the fixed delay with waitForSharedImagePluginReady(), which
probes the plugin via a read-only hasSharedImage() call and retries
within a bounded budget (10 attempts, 300ms apart) until the plugin
actually responds. The initial check runs only once readiness is
confirmed, with a best-effort fallback if the budget is exhausted.
Scope is limited to the initial startup check on iOS. appStateChange,
appUrlOpen/handleDeepLink, router navigation, share processing, Android
behavior, and all native Swift code are unchanged. Temporary
share-target diagnostics are preserved and extended with startup
readiness logging.
Document the change as a Phase 2A section in
doc/share-target-ios-audit.md.
Add a lightweight, temporary trace logger to diagnose where the iOS
Share Extension stops executing during a failed cold-start share.
- ShareViewController: add appendTrace() helper that writes ISO8601-
prefixed lines to share-extension-trace.log in the App Group
container, ignoring failures (diagnostics only)
- Add trace entries across the share flow: viewDidLoad,
processAndOpenApp, processSharedImage, image attachment/load,
storeImageData, setSharedPhotoReadyFlag, openMainApp, completeRequest
- SharedImageUtility: add getShareExtensionTrace() (read-only) and
clearShareExtensionTrace() (deletes the trace file)
- SharedImagePlugin: expose getShareExtensionTrace() and
clearShareExtensionTrace() to JS
- definitions.ts / SharedImagePlugin.web.ts: add ShareExtensionTrace
type, method signatures, and web stubs
Share behavior is unchanged and Android is untouched. All additions are
marked with "TEMPORARY SHARE TARGET DIAGNOSTICS".
Extend ShareExtensionDiagnostics with pendingShareExists across
SharedImageUtility, definitions, and the web stub, and log cold-start
state (pending share + current route) in the iOS startup share check.
Write shareExtensionLastStart on ShareViewController.viewDidLoad and
expose getShareExtensionDiagnostics() through SharedImagePlugin with
shareId, file path, and fileExists for debugging failed share flows.
Log start/end, shareId, attachment counts, UTType, byte counts, filenames,
and every early return across viewDidLoad through completeRequest to trace
how far a share progresses when debugging failures.
Log shareId, sharedPhotoFilePath, metadataExists, and fileExists at the
start of getSharedImageData() and hasSharedImage() to debug pending
App Group shares without changing retrieval behavior.
Stop deleting App Group metadata and image files in getSharedImageData()
so retrieval is read-only while preserving the existing plugin API shape.
Document removed deletion paths in the iOS share target audit.
Store shared images as <shareId>.<ext> in the App Group container while
keeping the original filename in metadata, preventing on-disk collisions
without changing retrieval, deletion, or JS consumer behavior.
Generate a UUID per incoming share in the Share Extension, persist it
as sharedPhotoShareId in App Group metadata, and add [ShareTarget] logs
for receive/store/retrieve events without changing retrieval or deletion.
Gitignored AppIcon.appiconset and Splash.imageset are absent after clone,
which made `capacitor-assets generate --ios` fail (missing paths and
Contents.json). Add ensure_ios_capacitor_asset_directories in common.sh
to mkdir and seed minimal Contents.json when needed; call it from
build-ios.sh before asset generation and from the build:native npm script.
Document the behavior in ios/.gitignore.
Exclude electron/src/rt/electron-plugins.js from clean in build-electron.sh
so the hand-maintained plugin list is not deleted. Update Podfile.lock
(TimesafariDailyNotificationPlugin 1.1.0 → 1.1.6) and electron
package-lock.json.
- iOS: set UNUserNotificationCenter delegate and implement willPresent
so notifications show in foreground and DailyNotificationDelivered is
posted for rollover; implement didReceive for tap handling; re-set
delegate in applicationDidBecomeActive
- Android: move DailyNotificationReceiver and BootReceiver inside
<application>; add NotifyReceiver; extend BootReceiver with
LOCKED_BOOT_COMPLETED, MY_PACKAGE_REPLACED, directBootAware
- main.capacitor: import daily-notification-plugin at startup so
plugin (and recovery) load on launch
- doc: add daily-notification-alignment-outline.md
Fixes foreground notifications not showing and rollover recovery;
Android receivers were previously declared outside <application>.
Add com.timesafari.dailynotification.fetch and com.timesafari.dailynotification.notify
to BGTaskSchedulerPermittedIdentifiers in Info.plist to resolve registration
rejection errors. The plugin was attempting to register these identifiers but
they were not declared in the app's Info.plist, causing iOS to reject the
background task registrations.
Fixes Xcode log errors:
- Registration rejected; com.timesafari.dailynotification.fetch is not advertised
- Registration rejected; com.timesafari.dailynotification.notify is not advertised
- Add podspec file for daily notification plugin with correct name
- Create TimesafariDailyNotificationPlugin.podspec to match Capacitor's
expected naming convention
- Podspec name must match Podfile reference for CocoaPods compatibility
- Update Podfile to reference TimesafariDailyNotificationPlugin
- Add automated fix script for podspec creation
- scripts/fix-daily-notification-podspec.sh creates podspec with correct
name before Capacitor sync
- Integrated into build-ios.sh build process
- Fix typo in package.json: change "pina" to "pinia" (^2.1.7)
Fixes:
- Vite build error: "Failed to resolve import 'pinia'"
- CocoaPods error: "No podspec found for 'TimesafariDailyNotificationPlugin'"
- CocoaPods error: "The name of the given podspec doesn't match the expected one"
The podspec file is created automatically during the build process to ensure
Capacitor sync can find the plugin with the expected name, while maintaining
compatibility with the actual podspec file name in the plugin package.
Remove JPEG conversion and preserve original image formats and filenames
to test support for all image file types. Refactor image processing logic
for clarity and maintainability.
Changes:
- Remove forced JPEG conversion (was using 0.9 compression quality)
- Preserve original filename extensions instead of forcing .jpg
- Refactor from 3 branches to 2 (removed unlikely UIImage branch)
- Replace if statement with guard for non-image attachment filtering
- Simplify error check from optional binding to nil check
- Extract filename extension helper method to reduce duplication
- Update default filename from "shared-image.jpg" to "shared-image"
The only conversion that may occur is to PNG (lossless) when raw data
cannot be read from a file URL, preserving image quality while ensuring
compatibility.
- Fix large images not loading from share sheet (exceeded UserDefaults 4MB limit)
- Store all shared images as files in App Group container instead of base64 in UserDefaults
- Fix image refresh when sharing new image while already on SharedPhotoView
- Add route query watcher to detect _refresh parameter changes
- Remove debug logging and redundant UIImage check
The previous implementation tried to store base64-encoded images in UserDefaults,
which failed for large images (HEIF images were often 6MB+ when base64 encoded). Now all images
are stored as files in the App Group container, with only the file path stored
in UserDefaults. This supports images of any size and provides consistent
behavior regardless of format.
Also fixes an issue where sharing a new image while already viewing
SharedPhotoView wouldn't refresh the displayed image.
Replace the buggy temp file polling mechanism with direct native-to-JS
communication via custom Capacitor plugins for iOS and Android. This
eliminates race conditions, file management complexity, and polling overhead.
BREAKING CHANGE: Removes backward compatibility with temp file approach.
Android minSdkVersion upgraded from 22 to 23.
Changes:
- iOS: Created SharedImagePlugin (CAPBridgedPlugin) and SharedImageUtility
- Uses App Group UserDefaults for data sharing between extension and app
- Implements getSharedImage() and hasSharedImage() methods
- Removes temp file writing/reading logic from AppDelegate
- Android: Created SharedImagePlugin with @CapacitorPlugin annotation
- Uses SharedPreferences for data storage instead of temp files
- Implements same interface as iOS plugin
- Removes temp file handling from MainActivity
- TypeScript: Added plugin definitions and registration
- Created SharedImagePlugin interface and web fallback
- Updated main.capacitor.ts to use plugin instead of Filesystem API
- Removed pollForFileExistence() and related file I/O code
- Android: Upgraded minSdkVersion from 22 to 23
- Required for SharedPreferences improvements and better API support
Benefits:
- Eliminates race conditions from file polling
- Removes file cleanup complexity
- Direct native-to-JS communication (no file I/O)
- Better performance (no polling overhead)
- More reliable data transfer between share extension and app
- Cleaner architecture with proper plugin abstraction
Fix share extension not appearing in share sheet on iOS 18.6 and earlier
versions by updating deployment target and resolving API availability issues.
Changes:
- Update share extension deployment target from 26.1 to 14.0
- iOS 18.6 cannot use extensions requiring iOS 26.1
- UTType API requires iOS 14.0+ (used in ShareViewController)
- Update share extension display name from "TimeSafariShareExtension" to
"TimeSafari" for cleaner appearance in share sheet
- Fix compiler warning: replace unused error binding with boolean check
(if let error = error → if error != nil)
The share extension now works on iOS 14.0+ (matching UTType requirements)
and displays properly in the share sheet on older iOS versions.
Remove unused Capacitor plugin implementation and simplify shared image
handling to use only the temp file approach, which is already working
reliably for both iOS and Android.
Changes:
- Delete ios/App/App/ShareImagePlugin.swift (unused, never registered)
- Delete ios/App/App/ShareImageBridge.swift (only used by plugin)
- Remove ~80 lines of plugin fallback code from src/main.capacitor.ts
- Simplify error handling to single code path
- Add documentation comment explaining temp file approach and future
plugin implementation possibility
Benefits:
- ~154 lines of code removed
- Single, consistent code path for both platforms
- Easier to maintain and debug
- No functional changes - temp file approach already working
The temp file approach uses Capacitor's Filesystem plugin to read shared
image data written by native code (AppDelegate on iOS, MainActivity on
Android). This is simpler and more reliable than the plugin approach,
which required registration and bridge setup.
Future improvement: Consider implementing Capacitor plugins for direct
native-to-JS communication if lower latency becomes a priority, though
the current approach performs well in production.
Improve iOS share extension implementation to skip interstitial UI and
fix issues with subsequent image shares not updating the view.
iOS Share Extension Improvements:
- Replace SLComposeServiceViewController with custom UIViewController
to eliminate interstitial "Post" button UI
- Use minimal URL (timesafari://) instead of deep link for app launch
- Implement app lifecycle detection via Capacitor appStateChange listener
instead of relying solely on deep links
Deep Link and Navigation Fixes:
- Remove "shared-photo" from deep link schemas (no longer needed)
- Add empty path URL handling for share extension launches
- Implement processing lock to prevent duplicate image processing
- Add retry mechanism (300ms delay) to handle race conditions with
AppDelegate writing temp files
- Use router.replace() when already on /shared-photo route to force refresh
- Clear old images from temp DB before storing new ones
- Delete temp file immediately after reading to prevent stale data
SharedPhotoView Component:
- Add route watcher (@Watch) to reload image when fileName query
parameter changes
- Extract image loading logic into reusable loadSharedImage() method
- Improve error handling to clear image state on failures
This fixes the issue where sharing a second image while already on
SharedPhotoView would display the previous image instead of the new one.