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.
Implement native Android share functionality to allow users to share
images from other apps directly to TimeSafari. This mirrors the iOS
share extension functionality and provides a seamless cross-platform
experience.
Changes:
- Add ACTION_SEND and ACTION_SEND_MULTIPLE intent filters to
AndroidManifest.xml to register the app as a share target for images
- Implement share intent handling in MainActivity.java:
- Process incoming share intents in onCreate() and onNewIntent()
- Read shared image data from content URI using ContentResolver
- Convert image to Base64 encoding
- Write image data to temporary JSON file in app's internal storage
- Use getFilesDir() which maps to Capacitor's Directory.Data
- Update src/main.capacitor.ts to support Android platform:
- Extend checkAndStoreNativeSharedImage() to check for Android platform
- Use Directory.Data for Android file operations (vs Directory.Documents for iOS)
- Add Android to initial load and app state change listeners
- Ensure shared image detection works on app launch and activation
The implementation follows the same pattern as iOS:
- Native layer (MainActivity) writes shared image to temp file
- JavaScript layer polls for file existence with exponential backoff
- Image is stored in temp database and user is navigated to SharedPhotoView
This enables users to share images from Gallery, Photos, and other apps
directly to TimeSafari on Android devices.
Replace hardcoded timeout with reliable file existence polling and extract
duplicate image storage code into reusable function.
File Polling Improvements:
- Replace hardcoded 300ms timeout with pollForFileExistence() function
- Use Filesystem.stat() to check file existence instead of guessing timing
- Implement exponential backoff (100ms, 200ms, 400ms, 800ms, 1600ms)
- Maximum 5 retries with configurable parameters
- More reliable: actually checks if file exists rather than fixed wait time
Code Deduplication:
- Extract storeSharedImageInTempDB() function to eliminate code duplication
- Consolidates clearing old data, base64-to-dataURL conversion, and DB storage
- Used in both temp file and plugin fallback code paths
- Improves maintainability and reduces risk of inconsistencies
This makes the code more robust and maintainable while ensuring the file
is actually available before attempting to read it.
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.
Implement iOS Share Extension to enable native image sharing from Photos
and other apps directly into TimeSafari. Users can now share images from
the iOS share sheet, which will open in SharedPhotoView for use as gifts
or profile pictures.
iOS Native Implementation:
- Add TimeSafariShareExtension target with ShareViewController
- Configure App Groups for data sharing between extension and main app
- Implement ShareViewController to process shared images and convert to base64
- Store shared image data in App Group UserDefaults
- Add ShareImageBridge utility to read shared data from App Group
- Update AppDelegate to handle shared-photo deep link and bridge data to JS
JavaScript Integration:
- Add checkAndStoreNativeSharedImage() in main.capacitor.ts to read shared
images from native layer via temporary file bridge
- Convert base64 data to data URL format for compatibility with base64ToBlob
- Integrate with existing SharedPhotoView component
- Add "shared-photo" to deep link validation schema
Build System:
- Integrate Xcode 26 / CocoaPods compatibility workaround into build-ios.sh
- Add run_pod_install_with_workaround() for explicit pod install
- Add run_cap_sync_with_workaround() for Capacitor sync (which runs pod
install internally)
- Automatically detect project format version 70 and apply workaround
- Remove standalone pod-install-workaround.sh script
Code Cleanup:
- Remove verbose debug logs from ShareViewController, AppDelegate, and
main.capacitor.ts
- Retain essential logger calls for production debugging
Documentation:
- Add ios-share-extension-setup.md with manual Xcode setup instructions
- Add ios-share-extension-git-commit-guide.md for version control best practices
- Add ios-share-implementation-status.md tracking implementation progress
- Add native-share-target-implementation.md with overall architecture
- Add xcode-26-cocoapods-workaround.md documenting the compatibility issue
The implementation uses a temporary file bridge (AppDelegate writes to Documents
directory, JS reads via Capacitor Filesystem plugin) as a workaround for
Capacitor plugin auto-discovery issues. This can be improved in the future by
properly registering ShareImagePlugin in Capacitor's plugin registry.
Resolves "No Scheme" issue in Xcode by adding a shared scheme file.
Capacitor doesn't automatically create shared schemes, so this needs
to be manually added and committed to version control.
- Fix contactMethods not being exported (was checking for string instead of array)
- Add missing notes and iViewContent fields to $insertContact method
- Normalize empty strings to null when saving contacts in ContactEditView
This ensures contact data integrity is maintained during export/import operations.
Update the section title from "Data Export" to "Data Management" to better reflect that the component handles both data export and import functionality.
Modified selectProject() to only set receiver to "You" if no receiver
has been selected yet, preventing recipient from being reset when
changing giver project in Project-to-Person context.
Remove loadMoreCallback prop and related backward compatibility code.
No parent components were using this prop, and it has been superseded
by the internal pagination mechanism using fetchProjects() and beforeId.
- Move loadProjects() call from created() to handleDialogOpen()
- Remove allProjects check from ensureSelectedProjectLoaded()
- Projects now load only when dialog is opened, improving initial page load performance
- ensureSelectedProjectLoaded() now directly fetches project by handleId when needed
Refactor selectedProject computation to use separate storage instead of
relying on allProjects array. This fixes a bug where the selected project
wouldn't display after page refresh if it wasn't in the initial allProjects
batch.
Changes:
- Add selectedProjectData property to store selected project independently
- Simplify selectedProject computed to return selectedProjectData directly
- Add fetchProjectByHandleId() to fetch single project by handleId
- Add ensureSelectedProjectLoaded() to check allProjects first, then fetch
- Update handleProjectLinkAssigned() to store directly in selectedProjectData
- Remove band-aid solution of adding selected projects to allProjects array
- Update startEditing() and cancelEditing() to ensure selected project loads
- Call ensureSelectedProjectLoaded() in created() lifecycle hook
This ensures the selected project always displays correctly, even when:
- Selected from search results (not in allProjects)
- Page is refreshed (allProjects reloads without selected project)
- Project is in a later pagination batch
Implement server-side search for projects using API endpoint with
pagination support via beforeId parameter. Contacts continue using
client-side filtering from complete local database.
- Add PlatformServiceMixin for internal apiServer access
- Implement performProjectSearch() with pagination
- Update infinite scroll to handle search pagination
- Add search lifecycle management and error handling
No breaking changes to parent components.
Add server-side pagination to EntityGrid component for projects, enabling
infinite scrolling to load all available projects instead of stopping after
the initial batch.
Changes:
- EntityGrid: Add loadMoreCallback prop to trigger server-side loading when
scroll reaches end of loaded projects
- OnboardMeetingSetupView: Update loadProjects() to support pagination with
beforeId parameter and add handleLoadMoreProjects() callback
- MeetingProjectDialog: Accept and pass through loadMoreCallback to EntityGrid
- GiftedDialog: Add pagination support to loadProjects() and
handleLoadMoreProjects() callback
- EntitySelectionStep: Accept and pass through loadMoreCallback prop to
EntityGrid when showing projects
This ensures users can access all projects in MeetingProjectDialog and
GiftedDialog by automatically loading more as they scroll, matching the
behavior already present in DiscoverView.
All project uses of EntityGrid now use pagination by default.
Replace Project Link text input with interactive selection dialog
using new MeetingProjectDialog component. Dialog displays user's
projects with icons and issuer information, following the same
pattern as ProjectRepresentativeDialog.
- Create MeetingProjectDialog with EntityGrid integration
- Add clickable project field with icon, name, and issuer display
- Load projects from /api/v2/report/plansByIssuer endpoint
- Show issuer name instead of handleId for better UX
- Refactor loadProjects to remove unused rowId field
EntityGrid's recentContacts assumes contacts are sorted by date added
(newest first), but ProjectRepresentativeDialog was receiving contacts
sorted alphabetically from NewEditProjectView, causing it to show
different "Recently Added" contacts than GiftedDialog.
- Changed NewEditProjectView to use $contactsByDateAdded() instead of
$getAllContacts()
- Added documentation comments to EntityGrid.vue to prevent this issue
in future reuses
- Remove conflictChecker prop (always passed as no-op function)
- Remove unused emitCancel method and cancel event handling
- Simplify handleEntitySelected by removing unnecessary type check
- Update NewEditProjectView to remove conflict-checker binding and empty cancel handler
The conflictChecker prop was not needed since representative selection
doesn't require conflict detection. The cancel event was never emitted
and the parent handler was empty, so both were removed.
Handle "Connection already exists" error when initializing SQLite database
on Capacitor platforms. The native connection can persist across app
restarts while the JavaScript connection Map is empty, causing a mismatch.
When createConnection fails with "already exists":
- Check if connection exists in JavaScript Map and retrieve it if present
- If not in Map, close the native connection and recreate to sync both sides
- Handle "already open" errors gracefully when opening existing connections
This fixes the issue where clicking "Backup Identifier Seed" would redirect
to StartView instead of SeedBackupView due to database initialization
failures in the router navigation guard.
Fixes navigation issue on both iOS and Android platforms.
Replace the plain text input for authorized representative with an
interactive contact selection interface that provides better UX and
maintains data consistency.
Changes:
- Add ProjectRepresentativeDialog component using EntityGrid for contact selection (excludes "You" and "Unnamed" special entities)
- Replace text input with clickable field showing contact icon, name, and DID
- Implement conditional UI states: initial "Assign..." placeholder vs assigned representative display with unset button
- Refactor selectedRepresentative to computed property derived from agentDid (single source of truth, prevents sync issues)
- Inline representativeDisplayName for simplicity
- Support changing representative by clicking on assigned field
- Support unsetting representative via trash button
The new implementation ensures agentDid remains the authoritative state while selectedRepresentative is automatically computed, preventing the previously possible desync when agentDid was set directly (e.g., via the
"make original owner an authorized representative" button).