When no shared image exists, return a bare Capacitor resolve instead of
an object with null base64/fileName/shareId fields, matching the
TypeScript contract and web implementation.
Add SharedImageUtility.clearProcessedShare() to permanently delete the
shared image file and remove all App Group metadata (sharedPhotoFileName,
sharedPhotoFilePath, sharedPhotoShareId, sharedPhotoReady,
sharedPhotoProcessedShareId). The helper is idempotent and logs cleanup
started/completed plus a warning on file-deletion failure. Expose it through
the SharedImage plugin with a web no-op stub.
Wire cleanup into the import flow so it runs only after both
storeSharedImageInTempDB() and markProcessed() succeed; a cleanup failure is
logged but does not fail the already-successful import. After cleanup a
subsequent getSharedImage() finds no pending share. iOS only; Android, the
Share Extension, and Phase 1E duplicate suppression are unchanged.
Return nil from getSharedImageData() when the current sharedPhotoShareId
equals the stored sharedPhotoProcessedShareId, so a share that was already
marked processed is no longer re-imported on subsequent app activations.
The check runs before reading the image file and logs
"[ShareTarget] shareId=<id> already processed; skipping". It leaves the
image file and all metadata intact (cleanup is deferred to a later phase),
and does not change behavior for brand-new shares. iOS only; no changes to
markProcessed(), the Share Extension, Android, or the JS flow.
Include the existing native sharedPhotoShareId in the SharedImage
getSharedImage() result so the web layer can identify the current share.
Add shareId to the native result dictionary and plugin response (NSNull on
the no-image branch), and add shareId: string to the TypeScript
SharedImageResult interface.
This surfaces a value that was already produced natively (previously used
only for logging) so application code can later pass it to markProcessed().
No behavior changes beyond exposing the value; Android untouched. Updates
doc/share-target-ios-audit.md accordingly.
Add a native iOS markProcessed(shareId) method to the SharedImage plugin
that records an explicit completion signal in App Group UserDefaults
(sharedPhotoProcessedShareId) and logs "[ShareTarget] shareId=<id> marked
processed". The API is exposed to TypeScript via the plugin definitions and
a web no-op stub.
Deliberately does not delete files or clear share metadata (cleanup is
deferred to a later phase), is not yet called from application code, and
does not touch Android. Documents the share-target lifecycle and plugin API
in doc/share-target-ios-audit.md.
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.