# iOS Share Target Audit **Date:** 2026-06-30 **Platform:** iOS only (Android unchanged) **Status:** Phase 1D complete — explicit share completion API added (not yet called) ## Purpose This document tracks the native iOS share-target lifecycle and the Capacitor API surface exposed to the web/TypeScript layer. It is updated as each phase lands. ## Lifecycle Overview 1. The Share Extension (`ShareViewController.swift`) receives an image, writes it to the App Group container, records metadata in App Group `UserDefaults`, assigns a `shareId` (UUID), sets the `sharedPhotoReady` flag, and opens the main app via `timesafari://`. 2. The main app reads the shared image through the `SharedImage` Capacitor plugin. 3. The application layer processes the image, then (in a future phase) signals explicit completion via `markProcessed(shareId)`. ## App Group Storage Keys Stored in `UserDefaults(suiteName: "group.app.trentlarson.timesafari.share")`: | Key | Set by | Meaning | | --- | --- | --- | | `sharedPhotoFileName` | Share Extension | Original display filename | | `sharedPhotoFilePath` | Share Extension | On-disk filename in App Group container | | `sharedPhotoShareId` | Share Extension | Current pending share identifier (UUID) | | `sharedPhotoReady` | Share Extension | Flag indicating a share is ready | | `sharedPhotoProcessedShareId` | Main app (`markProcessed`) | Last shareId explicitly marked processed (Phase 1D) | ## Capacitor API (`SharedImage` plugin) The plugin is registered as `SharedImage` and is implemented in: - Native iOS: `ios/App/App/SharedImagePlugin.swift` (delegates to `ios/App/App/SharedImageUtility.swift`) - Web stub: `src/plugins/SharedImagePlugin.web.ts` - TypeScript definitions: `src/plugins/definitions.ts` ### `getSharedImage(): Promise` Returns the shared image as `{ base64, fileName, shareId }`, or `null` if none exists. `shareId` is the native `sharedPhotoShareId` (UUID assigned by the Share Extension); pass it to `markProcessed()` to signal explicit completion. Read-only: native metadata and the file are left intact after retrieval (Phase 1C). > Note: On iOS the "no shared image" case resolves an object with `base64`, > `fileName`, and `shareId` set to `null`; on web `getSharedImage()` resolves > `null` directly. ### `hasSharedImage(): Promise<{ hasImage: boolean }>` Returns whether a shared image file currently exists, without reading it. ### `markProcessed(options: { shareId: string }): Promise<{ success: boolean }>` — Phase 1D Explicit completion signal from the application layer for a given share. - **Input:** `shareId` — the identifier reported by `getSharedImage()`. Must be a non-empty string; otherwise the call rejects. - **Behavior (iOS):** Records `shareId` in App Group `UserDefaults` under `sharedPhotoProcessedShareId` and resolves with `{ success: true }`. - **Does NOT delete files:** The stored image file and share metadata are intentionally left intact. File cleanup is deferred to a later phase. - **Web:** No-op; resolves with `{ success: false }`. - **Logging:** Emits `[ShareTarget] shareId= marked processed` on success. > Note (Phase 1D): `markProcessed` is implemented and exposed to TypeScript > but is **not yet called** from application code. Wiring it into the > processing flow is a later phase. ## Native Logging Reference Relevant `[ShareTarget]` log lines emitted on iOS: - `[ShareTarget] shareId= retrieved` - `[ShareTarget] shareId= left intact after retrieval` - `[ShareTarget] shareId= marked processed` (Phase 1D) ## Out of Scope - Android: no changes. The Android plugin (`android/app/src/main/java/app/timesafari/sharedimage/SharedImagePlugin.java`) does not implement `markProcessed`. - File deletion / cleanup of processed shares. - Calling `markProcessed` from application code.