one of my favorite things about reading is coming across new, interesting, weirdly specific words. i wanted a nice home for them, so just for fun i made a lil dictionary app to collect words and expressions i want to remember.
Sorry for the delayed release on this one. Usually we like to push out a release every week or two, but 1.7 was a notable exception; it took us 3 months to ship. Some details for anyone who’s curious.
Sometimes big underlying architecture changes need to be made in order to introduce new features and improvements.
For example, with this 1.7 release and the introduction of our Bloom Engine, we overhauled our media item processing pipeline in order to allow for features like text recognition.
If there’s no existing user data, making and pushing out changes can be done more easily. But if there’s user data, then extra thought needs to be put in to figure out how to roll out these changes gracefully. Therefore each new big change that carries promise comes with a significant time/energy tax (sometimes 200% / 2x the amount of time it takes to implement the major changes!). As a result, we’re naturally disincentivized from pushing out such major changes frequently. This is not good, so we wanted to spend some time figuring out how to remove this friction.
Migrating data on an app that uses a centralized database (eg. a web app, which is the world we come from) would more predictable because we can run a migration script on a central server. But Gather is local-first, and user data is privately stored in their own iCloud accounts (we have no access/visibility to user data), so migrations need to run decentralized-ly, piecemeal, one device at a time. There are unique scenarios that we have to contemplate when a migration script runs this way:
- The device turns off, or the network cuts out, or the battery dies during migration
- User upgrades Gather on multiple devices simultaneously, or a long stretch of time apart (each of which causes their own issues)
- User skips upgrading a version of the app on one or all devices
So over the course of a couple months, we built a reusable migration harness. This harness takes care of some of these above details in a recyclable way, while the specifics of the migration are isolated to a less-complex script. Our migration harness, as of right now performs the following steps:
1. Initialize, checks if migration is needed
2. If the migration is major, then we block access to the main app UI until the migration is completed. This is rare though (1.7 being our first exception), and most of the time, the migration can run in the background while the user is using the app.
3. Ensure network connection is available
4. Check if migration was run on this device previously but was interrupted, creates a resume state if so
5. Check if an active migration is running on one of the users’ other devices, and if so waits for that to finish
6. Wait for current iCloud/CloudKit import/export operations to finish
7. Device turns on a “migration lock”, so that other devices don’t try to migrate while this device has the migration lock
8. Run the version-specific migration script. (eg. in 1.7 we update the media item data schema and perform text recognition retroactively on existing media items). Migration scripts emit their progress (and errors) to the migration harness.
9. Wait for iCloud/CloudKit export operations to complete
10. Turn off migration lock, set the migration status for this device to “complete”
11. If necessary, unblock access to the main app UI
We also created a bunch of mock migration scripts that allowed us to build and test this harness extensively, which is what you see in this lil video below.
So far, this work seems to be working fairly well. When @steven_yuen ran the prod version of 1.7 on his Mac, the app crashed once mid-migration, but c’mon, the man has thousands of media items. Still, when he restarted the app, the migration picked up where it left off, and the migration completed gracefully. No doubt there are improvements that can be made (eg. would love to eliminate our dependence on a network connection for migrations given Gather is local-first), but for now, this drastically reduces the “big change tax” that we previously felt.
Tldr; How fast we can push out major changes/features is limited by how easily we can perform data migrations. So we spent a bunch of time creating a migration framework that allows us to hopefully make and push out big changes faster and more reliably.
Gather 1.7 is now available
This release introduces what we’re calling Gather’s Bloom Engine. The Bloom Engine is Gather’s brain - it’s designed to make your content more useful, more organized, and more powerful. We think of what you’ve added to Gather as seeds that grow over time - that become something bigger and more meaningful. Over time, the Bloom Engine will learn and understand your context, create connections, make suggestions, and enrich everything you’ve saved and created in Gather. It works invisibly in the background, offline, locally, and privately on your device. Much more to come in future releases.
Bloom Engine: Text Recognition
The first feature of the Bloom Engine that we are releasing is automatic text recognition on your Library media items. All existing photos in your Library will be automatically scanned for text and indexed so that you can search for photos based on the detected text.
Task Improvements
We’ve added a few small features to make working with tasks easier. You can now postpone tasks through the right-click (macOS) or long tap (iOS) menu. You can also edit tasks easily and inline by simply clicking/tapping on the task text. Finally, if you’re using macOS, you’re now able to select multiple tasks (using the Option key) and deleting or postponing the selected tasks in one go.
General fixes and improvements:
- You can now convert a task to a thought (and vice versa) through the right-click (macOS) or long tap (iOS) menu in the Journal
- Search now prioritizes note title matches higher than body searches
- Text entered into Create text field is now automatically backed up and restored in case of accidental dismissal
- You can now search for text within the current note body using Cmd+F (macOS)
- Reliability improvements to the Tag field in the Share Extension
- Fixed: Media Library built-in web browser can now follow links that would ordinarily open a new tab/window
- Fixed: Web browser in Media Library would sometimes crash when opening a link or browsing