From 02df6e20eb5e51d0f6b472c880df04fbaba4c6c5 Mon Sep 17 00:00:00 2001 From: MiMoHo Date: Thu, 30 Jul 2026 14:43:50 +0200 Subject: [PATCH] saveEngine persisted the favorites list as the clipping history -saveEngine wrote the current clippingStore under the "jcList" key. While the favorites store is swapped in, clippingStore IS favoritesStore and the main store is held in stashedStore, so the favorites list was persisted as the clipping history and the real history was never written at all. With savePreference at "after each change" - which is what the preferences offer and what -actionAfterListModification acts on - this needs no quit: one 'f' in the bezel and one copy is enough, because a copy made while the store is swapped lands in the favorites store and triggers the save. Quitting does it too, since -applicationWillTerminate saves. Switching back does not repair it either: -saveStore:toKey:onDict: clears modifiedSinceLastSaveStore on the store it wrote, and while swapped that cleared both flags, so the guard then skipped every later save. Resolve the main store from wherever it currently is and use it for both the guard and the "jcList" key. favoritesStore keeps writing "favoritesList", so the favorites themselves were never affected and still are not. The guard change is not incidental. On master it tests clippingStore and favoritesStore, which are the same object while swapped, so a session in which only the main store changed was skipped entirely and fell back to the last saved state - a second, quieter way to lose clippings. It now tests the main store. Verified with a test program compiled twice, once against this file as it is on master and once against this version, run against an isolated defaults domain (history seeded, then 'f', then a copy, then terminate): master: jcList <- the favorites list, real history never written 9 clippings in, 1 clipping back after the next launch patched: jcList <- the real history, favoritesList <- the favorites 9 clippings in, 9 back Also verified: savePreference == 0 is untouched (saveEngine is not called on that path), and a main store modified before the swap is now saved instead of being dropped. Builds with Xcode 26.5, 76 warnings, identical to master. Co-Authored-By: Claude --- FlycutOperator.m | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/FlycutOperator.m b/FlycutOperator.m index ca5c581..c2c3d3d 100644 --- a/FlycutOperator.m +++ b/FlycutOperator.m @@ -1078,8 +1078,20 @@ - (void)saveStore:(FlycutStore *)store toKey:(NSString *)key onDict:(NSMutableDi } -(void) saveEngine { + // While the favorites store is swapped in, clippingStore IS favoritesStore and the main + // store is held in stashedStore, so persist the main store from wherever it currently + // is. Saving clippingStore under "jcList" wrote the favorites list as the clipping + // history and never wrote the real history at all - and with savePreference at "after + // each change" that happens on the very next copy, no quit required. + // + // This relies on -switchToFavoritesStore / -restoreStashedStore keeping stashedStore + // non-NULL exactly while clippingStore is favoritesStore. A re-entrant + // -switchToFavoritesStore would break that (it would stash the favorites store over the + // main one), so keep that invariant in mind when touching either method. + FlycutStore *mainStore = ( NULL != stashedStore ) ? stashedStore : clippingStore; + // saveEngine saves to NSUserDefaults. If there have been no modifications, just skip this to avoid busy activity for any observers. - if ( !([clippingStore modifiedSinceLastSaveStore] + if ( !([mainStore modifiedSinceLastSaveStore] || [favoritesStore modifiedSinceLastSaveStore]) ) return; @@ -1093,7 +1105,7 @@ -(void) saveEngine { [saveTarget performSelector:saveSelector withObject:saveDict]; - [self saveStore:clippingStore toKey:@"jcList" onDict:saveDict]; + [self saveStore:mainStore toKey:@"jcList" onDict:saveDict]; [self saveStore:favoritesStore toKey:@"favoritesList" onDict:saveDict]; [[NSUserDefaults standardUserDefaults] setObject:saveDict forKey:@"store"];