From 56a92e79387538dd1f10d652ab8ef6e69c4e866b Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Thu, 3 Oct 2019 13:30:43 +1000 Subject: [PATCH 1/2] Adds unit testing and implements fixes for unread counts, icons, missing metadata and more. --- .../Account/Account.xcodeproj/project.pbxproj | 96 ++++++ .../xcshareddata/xcschemes/Account.xcscheme | 77 +++++ .../Feedly/AccountFeedlySyncTest.swift | 322 ++++++++++++++++++ .../feedly_collections_addcollection.json | 1 + .../newcollection_addcollection.json | 1 + .../AddFeed/feedly_collections_addfeed.json | 1 + .../Feedly/AddFeed/mustread_addfeed.json | 1 + .../Feedly/FeedlyResourceIdTests.swift | 27 ++ .../Initial/feedly_collections_initial.json | 1 + .../Feedly/Initial/gobalall_initial.json | 1 + .../Feedly/Initial/macintosh_initial.json | 1 + .../Feedly/Initial/mustread_initial.json | 1 + .../Initial/newcollection_addcollection.json | 1 + .../Feedly/Initial/programming_initial.json | 1 + .../Feedly/Initial/uncategorized_initial.json | 1 + .../Feedly/Initial/weblogs_initial.json | 1 + .../AccountTests/TestAccountManager.swift | 11 +- Frameworks/Account/DataExtensions.swift | 6 +- .../Account/Feedly/FeedlyAPICaller.swift | 4 +- .../Feedly/FeedlyAccountDelegate.swift | 8 +- .../FeedlyArticleStatusCoordinator.swift | 30 +- .../Account/Feedly/Models/FeedlyEntry.swift | 18 +- .../Feedly/Models/FeedlyEntryParser.swift | 103 ++++++ .../Account/Feedly/Models/FeedlyFeed.swift | 1 + .../Account/Feedly/Models/FeedlyLink.swift | 18 + .../Feedly/Models/FeedlyResourceId.swift | 37 ++ .../Account/Feedly/Models/FeedlyTag.swift | 14 + ...teFeedsForCollectionFoldersOperation.swift | 25 +- .../FeedlyGetStreamParsedItemsOperation.swift | 27 +- Frameworks/Account/Folder.swift | 8 + .../AccountsFeedlyWebWindowController.swift | 5 +- 31 files changed, 783 insertions(+), 66 deletions(-) create mode 100644 Frameworks/Account/Account.xcodeproj/xcshareddata/xcschemes/Account.xcscheme create mode 100644 Frameworks/Account/AccountTests/Feedly/AccountFeedlySyncTest.swift create mode 100644 Frameworks/Account/AccountTests/Feedly/AddCollection/feedly_collections_addcollection.json create mode 100644 Frameworks/Account/AccountTests/Feedly/AddCollection/newcollection_addcollection.json create mode 100644 Frameworks/Account/AccountTests/Feedly/AddFeed/feedly_collections_addfeed.json create mode 100644 Frameworks/Account/AccountTests/Feedly/AddFeed/mustread_addfeed.json create mode 100644 Frameworks/Account/AccountTests/Feedly/FeedlyResourceIdTests.swift create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/feedly_collections_initial.json create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/gobalall_initial.json create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/macintosh_initial.json create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/mustread_initial.json create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/newcollection_addcollection.json create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/programming_initial.json create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/uncategorized_initial.json create mode 100644 Frameworks/Account/AccountTests/Feedly/Initial/weblogs_initial.json create mode 100644 Frameworks/Account/Feedly/Models/FeedlyEntryParser.swift create mode 100644 Frameworks/Account/Feedly/Models/FeedlyLink.swift create mode 100644 Frameworks/Account/Feedly/Models/FeedlyResourceId.swift create mode 100644 Frameworks/Account/Feedly/Models/FeedlyTag.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index a27cac954..205b75f94 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -75,6 +75,11 @@ 84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */; }; 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F73CF0202788D80000BCEF /* ArticleFetcher.swift */; }; 9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E12B01F2334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift */; }; + 9E1773D32345700F0056A5A8 /* FeedlyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D22345700E0056A5A8 /* FeedlyLink.swift */; }; + 9E1773D5234570E30056A5A8 /* FeedlyEntryParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D4234570E30056A5A8 /* FeedlyEntryParser.swift */; }; + 9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */; }; + 9E1773D923458D590056A5A8 /* FeedlyResourceId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */; }; + 9E1773DB234593CF0056A5A8 /* FeedlyResourceIdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */; }; 9E1D154D233370D800F4944C /* FeedlySyncStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */; }; 9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */; }; 9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */; }; @@ -85,6 +90,17 @@ 9E1D155B2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */; }; 9E1D155D233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */; }; 9E713653233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */; }; + 9E7F15072341E96700F860D1 /* AccountFeedlySyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F15062341E96700F860D1 /* AccountFeedlySyncTest.swift */; }; + 9E7F150A2341EF5A00F860D1 /* feedly_collections_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E7F15092341EF5A00F860D1 /* feedly_collections_initial.json */; }; + 9E7F150D2341F32000F860D1 /* macintosh_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E7F150C2341F32000F860D1 /* macintosh_initial.json */; }; + 9E7F15112341F39A00F860D1 /* uncategorized_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E7F15102341F39A00F860D1 /* uncategorized_initial.json */; }; + 9E7F15132341F3D900F860D1 /* programming_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E7F15122341F3D900F860D1 /* programming_initial.json */; }; + 9E7F15152341F42000F860D1 /* weblogs_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E7F15142341F42000F860D1 /* weblogs_initial.json */; }; + 9E7F15172341F48900F860D1 /* mustread_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E7F15162341F48900F860D1 /* mustread_initial.json */; }; + 9E832B1E2343467900D83249 /* feedly_collections_addcollection.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E832B1D2343467900D83249 /* feedly_collections_addcollection.json */; }; + 9E832B202343476A00D83249 /* newcollection_addcollection.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E832B1F2343476A00D83249 /* newcollection_addcollection.json */; }; + 9E832B23234416B400D83249 /* feedly_collections_addfeed.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E832B22234416B400D83249 /* feedly_collections_addfeed.json */; }; + 9E832B25234416FF00D83249 /* mustread_addfeed.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E832B24234416FF00D83249 /* mustread_addfeed.json */; }; 9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */; }; 9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */; }; 9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */; }; @@ -215,6 +231,11 @@ 84EAC4812148CC6300F154AB /* RSDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84F73CF0202788D80000BCEF /* ArticleFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcher.swift; sourceTree = ""; }; 9E12B01F2334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCreateFeedsForCollectionFoldersOperation.swift; sourceTree = ""; }; + 9E1773D22345700E0056A5A8 /* FeedlyLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyLink.swift; sourceTree = ""; }; + 9E1773D4234570E30056A5A8 /* FeedlyEntryParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyEntryParser.swift; sourceTree = ""; }; + 9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyTag.swift; sourceTree = ""; }; + 9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceId.swift; sourceTree = ""; }; + 9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceIdTests.swift; sourceTree = ""; }; 9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStrategy.swift; sourceTree = ""; }; 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionsOperation.swift; sourceTree = ""; }; 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMirrorCollectionsAsFoldersOperation.swift; sourceTree = ""; }; @@ -225,6 +246,17 @@ 9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrganiseParsedItemsByFeedOperation.swift; sourceTree = ""; }; 9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyUpdateAccountFeedsWithItemsOperation.swift; sourceTree = ""; }; 9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRefreshStreamEntriesStatusOperation.swift; sourceTree = ""; }; + 9E7F15062341E96700F860D1 /* AccountFeedlySyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedlySyncTest.swift; sourceTree = ""; }; + 9E7F15092341EF5A00F860D1 /* feedly_collections_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = feedly_collections_initial.json; sourceTree = ""; }; + 9E7F150C2341F32000F860D1 /* macintosh_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = macintosh_initial.json; sourceTree = ""; }; + 9E7F15102341F39A00F860D1 /* uncategorized_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = uncategorized_initial.json; sourceTree = ""; }; + 9E7F15122341F3D900F860D1 /* programming_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = programming_initial.json; sourceTree = ""; }; + 9E7F15142341F42000F860D1 /* weblogs_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = weblogs_initial.json; sourceTree = ""; }; + 9E7F15162341F48900F860D1 /* mustread_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mustread_initial.json; sourceTree = ""; }; + 9E832B1D2343467900D83249 /* feedly_collections_addcollection.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = feedly_collections_addcollection.json; sourceTree = ""; }; + 9E832B1F2343476A00D83249 /* newcollection_addcollection.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = newcollection_addcollection.json; sourceTree = ""; }; + 9E832B22234416B400D83249 /* feedly_collections_addfeed.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = feedly_collections_addfeed.json; sourceTree = ""; }; + 9E832B24234416FF00D83249 /* mustread_addfeed.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mustread_addfeed.json; sourceTree = ""; }; 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegate.swift; sourceTree = ""; }; 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollection.swift; sourceTree = ""; }; 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeed.swift; sourceTree = ""; }; @@ -432,12 +464,56 @@ 5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */, 5107A09C227DE77700C7C3C5 /* TestTransport.swift */, 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */, + 9E7F15082341E97100F860D1 /* Feedly */, 51D58756227F62E300900287 /* JSON */, 848935061F62485000CEBD24 /* Info.plist */, ); path = AccountTests; sourceTree = ""; }; + 9E7F15082341E97100F860D1 /* Feedly */ = { + isa = PBXGroup; + children = ( + 9E7F15062341E96700F860D1 /* AccountFeedlySyncTest.swift */, + 9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */, + 9E7F150B2341F2A700F860D1 /* Initial */, + 9E832B1A234344DA00D83249 /* AddCollection */, + 9E832B21234416B400D83249 /* AddFeed */, + ); + path = Feedly; + sourceTree = ""; + }; + 9E7F150B2341F2A700F860D1 /* Initial */ = { + isa = PBXGroup; + children = ( + 9E7F15092341EF5A00F860D1 /* feedly_collections_initial.json */, + 9E7F150C2341F32000F860D1 /* macintosh_initial.json */, + 9E7F15162341F48900F860D1 /* mustread_initial.json */, + 9E7F15122341F3D900F860D1 /* programming_initial.json */, + 9E7F15102341F39A00F860D1 /* uncategorized_initial.json */, + 9E7F15142341F42000F860D1 /* weblogs_initial.json */, + ); + path = Initial; + sourceTree = ""; + }; + 9E832B1A234344DA00D83249 /* AddCollection */ = { + isa = PBXGroup; + children = ( + 9E832B1D2343467900D83249 /* feedly_collections_addcollection.json */, + 9E832B1F2343476A00D83249 /* newcollection_addcollection.json */, + ); + path = AddCollection; + sourceTree = ""; + }; + 9E832B21234416B400D83249 /* AddFeed */ = { + isa = PBXGroup; + children = ( + 9E832B22234416B400D83249 /* feedly_collections_addfeed.json */, + 9E832B24234416FF00D83249 /* mustread_addfeed.json */, + ); + path = AddFeed; + sourceTree = ""; + }; 9EA31339231E368100268BA0 /* Feedly */ = { isa = PBXGroup; children = ( @@ -473,12 +549,16 @@ 9EBC31B32338AC2E002A567B /* Models */ = { isa = PBXGroup; children = ( + 9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */, 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */, 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */, 9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */, + 9E1773D4234570E30056A5A8 /* FeedlyEntryParser.swift */, 9EAEC625233318400085D7C9 /* FeedlyStream.swift */, 9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */, 9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */, + 9E1773D22345700E0056A5A8 /* FeedlyLink.swift */, + 9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */, ); path = Models; sourceTree = ""; @@ -647,10 +727,20 @@ 5133230E2281089500C30F19 /* icons.json in Resources */, 51D5875B227F630B00900287 /* tags_add.json in Resources */, 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */, + 9E832B202343476A00D83249 /* newcollection_addcollection.json in Resources */, + 9E832B1E2343467900D83249 /* feedly_collections_addcollection.json in Resources */, + 9E7F15152341F42000F860D1 /* weblogs_initial.json in Resources */, 51D5875C227F630B00900287 /* tags_initial.json in Resources */, 51D5875A227F630B00900287 /* tags_delete.json in Resources */, 5165D71722821C2400D9D53D /* taggings_add.json in Resources */, + 9E832B23234416B400D83249 /* feedly_collections_addfeed.json in Resources */, 5165D71622821C2400D9D53D /* taggings_delete.json in Resources */, + 9E7F15112341F39A00F860D1 /* uncategorized_initial.json in Resources */, + 9E7F15132341F3D900F860D1 /* programming_initial.json in Resources */, + 9E832B25234416FF00D83249 /* mustread_addfeed.json in Resources */, + 9E7F150A2341EF5A00F860D1 /* feedly_collections_initial.json in Resources */, + 9E7F15172341F48900F860D1 /* mustread_initial.json in Resources */, + 9E7F150D2341F32000F860D1 /* macintosh_initial.json in Resources */, 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -691,6 +781,7 @@ 844B297D2106C7EC004020B3 /* Feed.swift in Sources */, 9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */, 9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */, + 9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */, 515E4EB62324FF8C0057B0E7 /* URLRequest+RSWeb.swift in Sources */, 5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */, 84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */, @@ -714,8 +805,10 @@ 552032FE229D5D5A009559E0 /* ReaderAPIAccountDelegate.swift in Sources */, 5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */, 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */, + 9E1773D923458D590056A5A8 /* FeedlyResourceId.swift in Sources */, 552032FC229D5D5A009559E0 /* ReaderAPIUnreadEntry.swift in Sources */, 9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */, + 9E1773D32345700F0056A5A8 /* FeedlyLink.swift in Sources */, 9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */, 84D09623217418DC00D77525 /* FeedbinTagging.swift in Sources */, 84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */, @@ -725,6 +818,7 @@ 846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */, 515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */, 844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */, + 9E1773D5234570E30056A5A8 /* FeedlyEntryParser.swift in Sources */, 9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */, 84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */, 9EBC31B7233987C1002A567B /* FeedlyArticleStatusCoordinator.swift in Sources */, @@ -736,12 +830,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9E7F15072341E96700F860D1 /* AccountFeedlySyncTest.swift in Sources */, 5165D7122282080C00D9D53D /* AccountFolderContentsSyncTest.swift in Sources */, 51D5875E227F643C00900287 /* AccountFolderSyncTest.swift in Sources */, 5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */, 513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */, 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */, 5107A099227DE42E00C7C3C5 /* AccountCredentialsTest.swift in Sources */, + 9E1773DB234593CF0056A5A8 /* FeedlyResourceIdTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Frameworks/Account/Account.xcodeproj/xcshareddata/xcschemes/Account.xcscheme b/Frameworks/Account/Account.xcodeproj/xcshareddata/xcschemes/Account.xcscheme new file mode 100644 index 000000000..f485b333a --- /dev/null +++ b/Frameworks/Account/Account.xcodeproj/xcshareddata/xcschemes/Account.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Frameworks/Account/AccountTests/Feedly/AccountFeedlySyncTest.swift b/Frameworks/Account/AccountTests/Feedly/AccountFeedlySyncTest.swift new file mode 100644 index 000000000..b196a8526 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/AccountFeedlySyncTest.swift @@ -0,0 +1,322 @@ +// +// AccountFeedlySyncTest.swift +// AccountTests +// +// Created by Kiel Gillard on 30/9/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import XCTest +@testable import Account +import Articles + +class AccountFeedlySyncTest: XCTestCase { + + private let testTransport = TestTransport() + private var account: Account! + + override func setUp() { + super.setUp() + + account = TestAccountManager.shared.createAccount(type: .feedly, transport: testTransport) + + do { + let username = UUID().uuidString + let credentials = Credentials(type: .oauthAccessToken, username: username, secret: "test") + try account.storeCredentials(credentials) + } catch { + XCTFail("Unable to register mock credentials because \(error)") + } + } + + override func tearDown() { + // Clean up + do { + try account.removeCredentials(type: .oauthAccessToken) + } catch { + XCTFail("Unable to clean up mock credentials because \(error)") + } + + TestAccountManager.shared.deleteAccount(account) + + super.tearDown() + } + + // MARK: Initial Sync + + func testInitialSync() { + XCTAssertTrue(account.idToFeedDictionary.isEmpty, "Expected to be testing a fresh account without any existing feeds.") + XCTAssertTrue((account.folders ?? Set()).isEmpty, "Expected to be testing a fresh account without any existing folders.") + + set(testFiles: .initial, with: testTransport) + + // Test initial folders for collections and feeds for collection feeds. + let initialExpection = self.expectation(description: "Initial feeds") + account.refreshAll() { _ in + initialExpection.fulfill() + } + waitForExpectations(timeout: 5) + + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_initial") + checkArticles(againstItemsInStreamInJSONNamed: "macintosh_initial") + checkArticles(againstItemsInStreamInJSONNamed: "mustread_initial") + checkArticles(againstItemsInStreamInJSONNamed: "programming_initial") + checkArticles(againstItemsInStreamInJSONNamed: "uncategorized_initial") + checkArticles(againstItemsInStreamInJSONNamed: "weblogs_initial") + } + + // MARK: Add Collection + + func testAddsFoldersForCollections() { + prepareBaseline(.initial) + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_initial") + + set(testFiles: .addCollection, with: testTransport) + + let addCollectionExpectation = self.expectation(description: "Adds NewCollection") + account.refreshAll() { _ in + addCollectionExpectation.fulfill() + } + waitForExpectations(timeout: 5) + + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_addcollection") + checkArticles(againstItemsInStreamInJSONNamed: "newcollection_addcollection") + } + + // MARK: Add Feed + + func testAddsFeeds() { + prepareBaseline(.addCollection) + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_addcollection") + checkArticles(againstItemsInStreamInJSONNamed: "mustread_initial") + + set(testFiles: .addFeed, with: testTransport) + + let addFeedExpectation = self.expectation(description: "Add Feed To Must Read (hey, that rhymes!)") + account.refreshAll() { _ in + addFeedExpectation.fulfill() + } + waitForExpectations(timeout: 5) + + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_addfeed") + checkArticles(againstItemsInStreamInJSONNamed: "mustread_addfeed") + } + + // MARK: Remove Feed + + func testRemovesFeeds() { + prepareBaseline(.addFeed) + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_addfeed") + checkArticles(againstItemsInStreamInJSONNamed: "mustread_addfeed") + + set(testFiles: .removeFeed, with: testTransport) + + let removeFeedExpectation = self.expectation(description: "Remove Feed from Must Read") + account.refreshAll() { _ in + removeFeedExpectation.fulfill() + } + waitForExpectations(timeout: 5) + + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_addcollection") + checkArticles(againstItemsInStreamInJSONNamed: "mustread_initial") + } + + func testRemoveCollection() { + prepareBaseline(.addFeed) + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_addfeed") + + set(testFiles: .removeCollection, with: testTransport) + + let removeCollectionExpectation = self.expectation(description: "Remove Collection") + account.refreshAll() { _ in + removeCollectionExpectation.fulfill() + } + waitForExpectations(timeout: 5) + + checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed: "feedly_collections_initial") + } + + // MARK: Utility + + func prepareBaseline(_ testFiles: TestFiles) { + XCTAssertTrue(account.idToFeedDictionary.isEmpty, "Expected to be testing a fresh accout.") + + set(testFiles: testFiles, with: testTransport) + + // Test initial folders for collections and feeds for collection feeds. + let preparationExpectation = self.expectation(description: "Prepare Account") + account.refreshAll() { _ in + preparationExpectation.fulfill() + } + waitForExpectations(timeout: 5) + } + + func checkFoldersAndFeeds(againstCollectionsAndFeedsInJSONNamed name: String) { + let collections = testJSON(named: name) as! [[String:Any]] + let collectionNames = Set(collections.map { $0["label"] as! String }) + let collectionIds = Set(collections.map { $0["id"] as! String }) + + let folders = account.folders ?? Set() + let folderNames = Set(folders.compactMap { $0.name }) + let folderIds = Set(folders.compactMap { $0.externalID }) + + let missingNames = collectionNames.subtracting(folderNames) + let missingIds = collectionIds.subtracting(folderIds) + + XCTAssertEqual(folders.count, collections.count, "Mismatch between collections and folders.") + XCTAssertTrue(missingNames.isEmpty, "Collections with these names did not have a corresponding folder with the same name.") + XCTAssertTrue(missingIds.isEmpty, "Collections with these ids did not have a corresponding folder with the same id.") + + for collection in collections { + checkSingleFolderAndFeeds(againstOneCollectionAndFeedsInJSONPayload: collection) + } + } + + func checkSingleFolderAndFeeds(againstOneCollectionAndFeedsInJSONNamed name: String) { + let collection = testJSON(named: name) as! [String:Any] + checkSingleFolderAndFeeds(againstOneCollectionAndFeedsInJSONPayload: collection) + } + + func checkSingleFolderAndFeeds(againstOneCollectionAndFeedsInJSONPayload collection: [String: Any]) { + let label = collection["label"] as! String + guard let folder = account.existingFolder(with: label) else { + // due to a previous test failure? + XCTFail("Could not find the \"\(label)\" folder.") + return + } + let collectionFeeds = collection["feeds"] as! [[String: Any]] + let folderFeeds = folder.topLevelFeeds + + XCTAssertEqual(collectionFeeds.count, folderFeeds.count) + + let collectionFeedIds = Set(collectionFeeds.map { $0["id"] as! String }) + let folderFeedIds = Set(folderFeeds.map { $0.feedID }) + let missingFeedIds = collectionFeedIds.subtracting(folderFeedIds) + + XCTAssertTrue(missingFeedIds.isEmpty, "Feeds with these ids were not found in the \"\(label)\" folder.") + } + + func checkArticles(againstItemsInStreamInJSONNamed name: String) { + let stream = testJSON(named: name) as! [String:Any] + checkArticles(againstItemsInStreamInJSONPayload: stream) + } + + func checkArticles(againstItemsInStreamInJSONPayload stream: [String: Any]) { + + struct ArticleItem { + var id: String + var feedId: String + var content: String + var JSON: [String: Any] + var unread: Bool + + /// Convoluted external URL logic "documented" here: + /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ + var externalUrl: String? { + return ((JSON["canonical"] as? [[String: Any]]) ?? (JSON["alternate"] as? [[String: Any]]))?.compactMap { link -> String? in + let href = link["href"] as? String + if let type = link["type"] as? String { + if type == "text/html" { + return href + } + return nil + } + return href + }.first + } + + init(item: [String: Any]) { + self.JSON = item + self.id = item["id"] as! String + + let origin = item["origin"] as! [String: Any] + self.feedId = origin["streamId"] as! String + + let content = item["content"] as? [String: Any] + let summary = item["summary"] as? [String: Any] + self.content = ((content ?? summary)?["content"] as? String) ?? "" + + self.unread = item["unread"] as! Bool + } + } + + let items = stream["items"] as! [[String: Any]] + let articleItems = items.map { ArticleItem(item: $0) } + let itemIds = Set(articleItems.map { $0.id }) + + let articles = account.fetchArticles(.articleIDs(itemIds)) + let articleIds = Set(articles.map { $0.articleID }) + + let missing = itemIds.subtracting(articleIds) + + XCTAssertEqual(items.count, articles.count) + XCTAssertTrue(missing.isEmpty, "Items with these ids did not have a corresponding article with the same id.") + + for article in articles { + for item in articleItems where item.id == article.articleID { + + XCTAssertEqual(article.uniqueID, item.id) + XCTAssertEqual(article.contentHTML, item.content) + XCTAssertEqual(article.feedID, item.feedId) + XCTAssertEqual(article.externalURL, item.externalUrl) + // XCTAssertEqual(article.status.boolStatus(forKey: .read), item.unread) + } + } + } + + func testJSON(named: String) -> Any { + let bundle = Bundle(for: TestTransport.self) + let url = bundle.url(forResource: named, withExtension: "json")! + let data = try! Data(contentsOf: url) + let json = try! JSONSerialization.jsonObject(with: data) + return json + } + + enum TestFiles { + case initial + case addCollection + case addFeed + case removeFeed + case removeCollection + } + + func set(testFiles: TestFiles, with transport: TestTransport) { + let endpoint = "https://sandbox7.feedly.com/v3" + let category = "\(endpoint)/streams/contents?unreadOnly=false&count=500&streamId=user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category" + + switch testFiles { + case .initial: + let dict = [ + "\(endpoint)/collections": "feedly_collections_initial.json", + "\(category)/5ca4d61d-e55d-4999-a8d1-c3b9d8789815": "macintosh_initial.json", + "\(category)/global.must": "mustread_initial.json", + "\(category)/885f2e01-d314-4e63-abac-17dcb063f5b5": "programming_initial.json", + "\(category)/66132046-6f14-488d-b590-8e93422723c8": "uncategorized_initial.json", + "\(category)/e31b3fcb-27f6-4f3e-b96c-53902586e366": "weblogs_initial.json", + ] + transport.testFiles = dict + + case .addCollection: + set(testFiles: .initial, with: transport) + + var dict = transport.testFiles + dict["\(endpoint)/collections"] = "feedly_collections_addcollection.json" + dict["\(category)/fc09f383-5a9a-4daa-a575-3efc1733b173"] = "newcollection_addcollection.json" + transport.testFiles = dict + + case .addFeed: + set(testFiles: .addCollection, with: transport) + + var dict = transport.testFiles + dict["\(endpoint)/collections"] = "feedly_collections_addfeed.json" + dict["\(category)/global.must"] = "mustread_addfeed.json" + transport.testFiles = dict + + case .removeFeed: + set(testFiles: .addCollection, with: transport) + + case .removeCollection: + set(testFiles: .initial, with: transport) + } + } +} diff --git a/Frameworks/Account/AccountTests/Feedly/AddCollection/feedly_collections_addcollection.json b/Frameworks/Account/AccountTests/Feedly/AddCollection/feedly_collections_addcollection.json new file mode 100644 index 000000000..51d3f58bd --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/AddCollection/feedly_collections_addcollection.json @@ -0,0 +1 @@ +[{"customizable":true,"feeds":[{"feedId":"feed/http://tidbits.com/feeds/tidbits_blurb.rss","id":"feed/http://tidbits.com/feeds/tidbits_blurb.rss","title":"TidBITS: Apple News for the Rest of Us","updated":1569876918424,"velocity":7.7,"subscribers":1,"website":"https://tidbits.com","language":"en","description":"Thoughtful, detailed coverage of everything Apple for 29 years\nand the TidBITS Content Network for Apple professionals"},{"feedId":"feed/http://www.macalope.com/feed/","id":"feed/http://www.macalope.com/feed/","title":"Macalope","updated":1498941877000,"velocity":0.0,"subscribers":1,"website":"http://www.macalope.com","language":"en","state":"dormant","description":"Full of sound and furry"},{"feedId":"feed/http://flyingmeat.com/blog/atom.xml","id":"feed/http://flyingmeat.com/blog/atom.xml","title":"The Flying Meat Weblog","updated":1343168154000,"velocity":0.0,"subscribers":1,"website":"https://flyingmeat.com/blog/","language":"en","state":"dormant"},{"feedId":"feed/http://www.macdrifter.com/feeds/all.atom.xml","id":"feed/http://www.macdrifter.com/feeds/all.atom.xml","title":"Macdrifter","updated":1561539243000,"velocity":0.1,"subscribers":1,"website":"http://www.macdrifter.com/","language":"en"},{"feedId":"feed/http://9to5mac.com/feed/","id":"feed/http://9to5mac.com/feed/","title":"9to5Mac","updated":1569916454064,"velocity":28.0,"subscribers":2,"website":"https://9to5mac.com","language":"en","description":"Apple News & Mac Rumors Breaking All Day"}],"label":"Macintosh","created":1569829941677,"enterprise":false,"numFeeds":5,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815"},{"customizable":false,"feeds":[],"label":"Must Read","created":1569417923847,"enterprise":false,"numFeeds":0,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must"},{"customizable":true,"feeds":[{"feedId":"feed/http://inessential.com/xml/rss.xml","id":"feed/http://inessential.com/xml/rss.xml","title":"Inessential","updated":1569829821629,"velocity":3.8,"subscribers":1,"website":"https://inessential.com/","language":"en","description":"Brent Simmons’s weblog."}],"label":"NewCollection","created":1569918572642,"enterprise":false,"numFeeds":1,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173"},{"customizable":true,"feeds":[{"feedId":"feed/http://www.codebykevin.com/blosxom.cgi/index.rss","id":"feed/http://www.codebykevin.com/blosxom.cgi/index.rss","title":"Code by Kevin","updated":1564803480000,"velocity":0.1,"subscribers":1,"website":"https://www.codebykevin.com/blosxom.cgi","language":"en","description":"Programming, code, business, and other pursuits"},{"feedId":"feed/http://www.raywenderlich.com/feed","id":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich","updated":1569891395210,"velocity":5.0,"subscribers":1,"language":"en"},{"feedId":"feed/http://ddeville.me/feed.xml","id":"feed/http://ddeville.me/feed.xml","title":"Damien DeVille","updated":1454187600000,"velocity":0.0,"subscribers":1,"website":"http://ddeville.me","language":"en","state":"dormant","description":"Software engineer at Dropbox. Previously at Realmac Software. UCL Computer Science alumnus."},{"feedId":"feed/http://owensd.io/rss.xml","id":"feed/http://owensd.io/rss.xml","title":"owensd.io - thoughts in and out - Articles","updated":1534837458000,"velocity":0.0,"subscribers":1,"website":"https://owensd.io","language":"en","state":"dormant","description":"A builder of things."},{"feedId":"feed/http://prog21.dadgum.com/atom.xml","id":"feed/http://prog21.dadgum.com/atom.xml","title":"Programming in the 21st Century","updated":1483509600000,"velocity":0.0,"subscribers":1,"website":"http://prog21.dadgum.com/","language":"en","state":"dormant"},{"feedId":"feed/http://feeds.feedburner.com/alistapart/main","id":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","updated":1569829877486,"velocity":4.5,"subscribers":1,"website":"https://alistapart.com","language":"en","description":"Articles for people who make web sites."},{"feedId":"feed/http://www.russbishop.net/feed","id":"feed/http://www.russbishop.net/feed","title":"Russ Bishop (atom)","updated":1551122826000,"velocity":0.0,"subscribers":1,"website":"http://www.russbishop.net/feed","language":"en","state":"dormant","description":"This blog represents my own personal opinion and is not endorsed by my employer."},{"feedId":"feed/http://mentalfaculty.tumblr.com/rss","id":"feed/http://mentalfaculty.tumblr.com/rss","title":"The Mental Blog","updated":1468311749000,"velocity":0.0,"subscribers":1,"website":"https://mentalfaculty.tumblr.com/","language":"en","state":"dormant","description":"Drew McCormack (@drewmccormack) is founder of The Mental Faculty, developer of Mental Case and the Ensembles sync framework"},{"feedId":"feed/http://blog.amyworrall.com/rss","id":"feed/http://blog.amyworrall.com/rss","title":"What Amy Did","updated":1486165238000,"velocity":0.0,"subscribers":1,"website":"https://blog.amyworrall.com/","language":"en","state":"dormant","description":"I’m a software developer from Coventry. I care about design and user experience. \n\nFollow me on Twitter: @amyruthworrall"},{"feedId":"feed/http://oleb.net/blog/atom.xml","id":"feed/http://oleb.net/blog/atom.xml","title":"Ole Begemann: iOS Development","updated":1559322747743,"velocity":0.1,"subscribers":1,"website":"https://oleb.net/blog/","language":"en"},{"feedId":"feed/http://subjc.com/atom.xml","id":"feed/http://subjc.com/atom.xml","title":"Subjective-C","updated":1461251700000,"velocity":0.0,"subscribers":2,"website":"http://subjc.com/","language":"en","state":"dormant"},{"feedId":"feed/http://robnapier.net/atom.xml","id":"feed/http://robnapier.net/atom.xml","title":"Cocoaphony","updated":1558929600000,"velocity":0.1,"subscribers":1,"website":"https://robnapier.net/","language":"en"},{"feedId":"feed/http://cocoamanifest.net/feeds/index.xml","id":"feed/http://cocoamanifest.net/feeds/index.xml","title":"Cocoa Manifest","updated":1402642440000,"velocity":0.0,"subscribers":2,"website":"http://cocoamanifest.net","language":"en","state":"dormant"},{"feedId":"feed/http://petersteinberger.com/atom.xml","id":"feed/http://petersteinberger.com/atom.xml","title":"Peter Steinberger","updated":1438088460000,"velocity":0.0,"subscribers":1,"website":"http://petersteinberger.com/","language":"en","state":"dormant"},{"feedId":"feed/http://www.weheartswift.com/feed/","id":"feed/http://www.weheartswift.com/feed/","title":"We ❤ Swift","updated":1569829929574,"velocity":0.2,"subscribers":1,"website":"https://www.weheartswift.com","language":"en","description":"Swift Tutorials and iOS development"},{"feedId":"feed/https://medium.com/feed/swift-programming","id":"feed/https://medium.com/feed/swift-programming","title":"Swift Programming — Medium","updated":1554317410000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/swift-programming?source=rss----5396e0e8bc29---4","language":"en","state":"dormant","description":"The Swift Programming Language - Medium"},{"feedId":"feed/http://tonyarnold.com/atom.xml","id":"feed/http://tonyarnold.com/atom.xml","title":"The blog of Tony Arnold","updated":1531312200000,"velocity":0.0,"subscribers":1,"website":"https://tonyarnold.com","language":"en","state":"dormant"},{"feedId":"feed/http://borkware.com/miniblog/rss/rss.xml","id":"feed/http://borkware.com/miniblog/rss/rss.xml","title":"Borkware Miniblog","updated":1360542004000,"velocity":0.0,"subscribers":1,"website":"https://borkwarellc.wordpress.com","language":"en","state":"dormant","description":"Bork bork bork bork."},{"feedId":"feed/http://nshipster.com/feed.xml","id":"feed/http://nshipster.com/feed.xml","title":"NSHipster","updated":1569418049005,"velocity":0.9,"subscribers":2,"website":"https://nshipster.com/","language":"en","description":"NSHipster is a journal of the overlooked bits in Objective-C, Swift, and Cocoa. Updated weekly."},{"feedId":"feed/http://feeds.feedburner.com/pilkyme","id":"feed/http://feeds.feedburner.com/pilkyme","title":"Pilky.me","updated":1561420800000,"velocity":0.1,"subscribers":1,"website":"https://pilky.me/","language":"en"},{"feedId":"feed/http://macoscope.com/blog/feed/","id":"feed/http://macoscope.com/blog/feed/","title":"[macoscope blog]","updated":1467803080000,"velocity":0.0,"subscribers":1,"website":"http://macoscope.com/blog","language":"en","state":"dormant","description":"The Macoscope Team on Designing and Developing Apps"},{"feedId":"feed/http://iosunittesting.com/feed/","id":"feed/http://iosunittesting.com/feed/","title":"iOS Unit Testing","updated":1436534796000,"velocity":0.0,"subscribers":1,"website":"http://iosunittesting.com","language":"en","state":"dormant","description":"It's about TDD, unit testing, and creating bug free code on iOS."},{"feedId":"feed/http://airspeedvelocity.net/feed/","id":"feed/http://airspeedvelocity.net/feed/","title":"Airspeed Velocity","updated":1452449244000,"velocity":0.0,"subscribers":1,"website":"https://airspeedvelocity.net","language":"en","state":"dormant","description":"African or European Swift?"},{"feedId":"feed/http://www.cimgf.com/feed/","id":"feed/http://www.cimgf.com/feed/","title":"Cocoa Is My Girlfriend","updated":1525988390000,"velocity":0.0,"subscribers":1,"website":"http://www.cimgf.com","language":"en","state":"dormant","description":"Taglines are for Windows programmers"},{"feedId":"feed/http://confusatory.org/rss","id":"feed/http://confusatory.org/rss","title":"The Confusatory","updated":1476982283000,"velocity":0.0,"subscribers":1,"website":"https://confusatory.org/","language":"en","state":"dormant","description":"cbowns’s tumblr."},{"feedId":"feed/http://indiestack.com/feed/","id":"feed/http://indiestack.com/feed/","title":"Indie Stack","updated":1569829987669,"velocity":0.2,"subscribers":1,"website":"https://indiestack.com","language":"en","description":"Hacking the Mac, iOS, and more with Daniel Jalkut"}],"label":"Programming","created":1569829874442,"enterprise":false,"numFeeds":26,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5"},{"customizable":true,"feeds":[{"feedId":"feed/http://feedpress.me/sixcolors","id":"feed/http://feedpress.me/sixcolors","title":"Six Colors","updated":1569872580344,"velocity":23.9,"subscribers":1,"website":"https://www.sixcolors.com/","language":"en","description":"Writing about Apple and other stuff by Jason Snell, Dan Moren, and others."},{"feedId":"feed/http://corinnekrych.blogspot.com/feeds/posts/default","id":"feed/http://corinnekrych.blogspot.com/feeds/posts/default","title":"chat & code","updated":1547807580000,"velocity":0.0,"subscribers":1,"website":"http://corinnekrych.blogspot.com/","language":"en","state":"dormant","description":"Code is craft and collaboration is key to success. I love chatting the latest tech trends at coffee break: female geek."},{"feedId":"feed/http://ericasadun.com/feed/","id":"feed/http://ericasadun.com/feed/","title":"Erica Sadun","updated":1569452593000,"velocity":0.7,"subscribers":1,"website":"https://ericasadun.com","language":"en","description":"Where technology meets something or other"},{"feedId":"feed/http://therecord.co/xml/rss.xml","id":"feed/http://therecord.co/xml/rss.xml","title":"The Record","updated":1401364800000,"velocity":0.0,"subscribers":1,"website":"http://therecord.co/","language":"en","state":"dormant","description":"The stories you should know about the Mac and Cocoa developer community. Hosted by Brent Simmons and Chris Parrish."},{"feedId":"feed/https://grokswift.com/feed/index.xml","id":"feed/https://grokswift.com/feed/index.xml","title":"Grok Swift","updated":1527175834000,"velocity":0.0,"subscribers":1,"website":"https://grokswift.com/","language":"en","state":"dormant"},{"feedId":"feed/https://blog.alltheflow.com/rss/","id":"feed/https://blog.alltheflow.com/rss/","title":"All The Flow","updated":1551711655000,"velocity":0.0,"subscribers":1,"website":"http://blog.alltheflow.com/","language":"en","state":"dormant","description":"Cocoa, Swift, tools, Auto Layout - with 🧡"},{"feedId":"feed/http://onefoottsunami.com/feed/atom/","id":"feed/http://onefoottsunami.com/feed/atom/","title":"One Foot Tsunami","updated":1569851526653,"velocity":4.7,"subscribers":1,"website":"https://onefoottsunami.com","language":"en","description":"Slightly less disappointing than it sounds"},{"feedId":"feed/http://www.loopinsight.com/feed/","id":"feed/http://www.loopinsight.com/feed/","title":"Loop Insight","updated":1569892117883,"velocity":6.5,"subscribers":1,"website":"https://www.loopinsight.com","language":"en","description":"Making Sense of Technology"},{"feedId":"feed/http://beckyhansmeyer.com/feed/","id":"feed/http://beckyhansmeyer.com/feed/","title":"Becky Hansmeyer","updated":1569087367000,"velocity":1.4,"subscribers":1,"website":"https://beckyhansmeyer.com","language":"en","description":"100% grass-fed Swift"},{"feedId":"feed/http://designatednerd.com/feed/","id":"feed/http://designatednerd.com/feed/","title":"Designated Nerd","updated":1564425470000,"velocity":0.1,"subscribers":1,"website":"http://designatednerd.com","language":"en","description":"Software and Technical Support"},{"feedId":"feed/http://appcamp4girls.com/blog?format=RSS","id":"feed/http://appcamp4girls.com/blog?format=RSS","title":"Blog - App Camp For Girls","updated":1558129314000,"velocity":0.1,"subscribers":1,"website":"https://appcamp4girls.com/blog/","language":"en"},{"feedId":"feed/https://inspiredmouse.com/feed/","id":"feed/https://inspiredmouse.com/feed/","title":"Inspired Mouse","updated":1511986957000,"velocity":0.0,"subscribers":1,"website":"https://inspiredmouse.com","language":"en","state":"dormant","description":"No project is too diminutive"},{"feedId":"feed/https://incrementalistblog.wordpress.com/feed/","id":"feed/https://incrementalistblog.wordpress.com/feed/","title":"The Incrementalist.","updated":1449808490000,"velocity":0.0,"subscribers":1,"website":"https://incrementalistblog.wordpress.com","language":"en","state":"dormant","description":"notes on design"},{"feedId":"feed/http://www.virginiaroberts.com/feed/","id":"feed/http://www.virginiaroberts.com/feed/","title":"Virginia Roberts","updated":1549571065000,"velocity":0.0,"subscribers":1,"website":"http://www.virginiaroberts.com","language":"en","state":"dormant"},{"feedId":"feed/http://jessysaurusrex.com/feed/","id":"feed/http://jessysaurusrex.com/feed/","title":"jessysaurusrex","updated":1521816327000,"velocity":0.0,"subscribers":1,"website":"https://jessysaurusrex.com","language":"en","state":"dormant","description":"I wear big necklaces and (attempt to) hack things."},{"feedId":"feed/http://blog.nicoleblee.com/feed/","id":"feed/http://blog.nicoleblee.com/feed/","title":"scattered thoughts","updated":1397441482000,"velocity":0.0,"subscribers":1,"website":"http://blog.nicoleblee.com","language":"en","state":"dormant","description":"(a collection of essays and other writings)"},{"feedId":"feed/https://medium.com/feed/@tessr","id":"feed/https://medium.com/feed/@tessr","title":"Tess Rinearson on Medium","updated":1501016354000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@tessr?source=rss-c16152863954------2","language":"en","state":"dormant","description":"Stories by Tess Rinearson on Medium"},{"feedId":"feed/https://medium.com/feed/@emarley","id":"feed/https://medium.com/feed/@emarley","title":"Liz Marley on Medium","updated":1514047622000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@emarley?source=rss-b4981c59ffa5------2","language":"en","state":"dormant","description":"Stories by Liz Marley on Medium"},{"feedId":"feed/http://blog.cocoabythefire.com/rss","id":"feed/http://blog.cocoabythefire.com/rss","title":"cocoa by the fire","updated":1488945726000,"velocity":0.0,"subscribers":1,"website":"https://blog.cocoabythefire.com/","language":"en","state":"dormant","description":"Hey there, I’m Brit! Coder, Entrepreneur, Daydreamer, Lucky Wife and Mom."},{"feedId":"feed/http://www.catehuston.com/blog/feed/","id":"feed/http://www.catehuston.com/blog/feed/","title":"Accidentally in Code","updated":1567641655000,"velocity":0.2,"subscribers":1,"website":"https://cate.blog","language":"en","description":"Engineering an Interesting Life"},{"feedId":"feed/http://www.aleenmean.com/feed.xml","id":"feed/http://www.aleenmean.com/feed.xml","title":"Aleen Mean","updated":1562374146000,"velocity":0.1,"subscribers":1,"website":"https://aleenmean.com/","language":"en","description":"Technology, diversity, and miscellaneous musings by Aleen Simms."},{"feedId":"feed/http://redqueencoder.com/feed/","id":"feed/http://redqueencoder.com/feed/","title":"The Red Queen Coder","updated":1547225214000,"velocity":0.0,"subscribers":1,"website":"http://redqueencoder.com","language":"en","state":"dormant","description":"If you give a person a program, you'll frustrate him for a day. If you teach a person to program, you will frustrate them for a lifetime!"},{"feedId":"feed/http://meaganwaller.com/index.php/feed/","id":"feed/http://meaganwaller.com/index.php/feed/","title":"Meagan Waller","updated":1403205537000,"velocity":0.0,"subscribers":1,"website":"https://meaganwaller.com","language":"en","state":"dormant"},{"feedId":"feed/http://www.myballard.com/feed/","id":"feed/http://www.myballard.com/feed/","title":"Ballard","updated":1569884140346,"velocity":7.5,"subscribers":1,"website":"https://www.myballard.com","language":"en","description":"News, events and restaurants for Seattle's Ballard and Fremont neighborhoods"},{"feedId":"feed/https://kateheddleston.com/blog/feed.atom","id":"feed/https://kateheddleston.com/blog/feed.atom","title":"KateHeddleston.com Blog Posts","updated":1526573156000,"velocity":0.0,"subscribers":1,"website":"https://www.kateheddleston.com/blog","language":"en","state":"dormant"},{"feedId":"feed/http://blog.erynwells.me/rss","id":"feed/http://blog.erynwells.me/rss","title":"Eryn Wells","updated":1442175535000,"velocity":0.0,"subscribers":1,"website":"https://blog.erynwells.me/","language":"en","state":"dormant"},{"feedId":"feed/http://lambdamaphone.blogspot.com/feeds/posts/default","id":"feed/http://lambdamaphone.blogspot.com/feeds/posts/default","title":"Everything in Context","updated":1519599000002,"velocity":0.0,"subscribers":1,"website":"http://lambdamaphone.blogspot.com/","language":"en","state":"dormant","description":"Game design, programming languages, and academia."},{"feedId":"feed/http://nothe.purplellamas.net/index.xml","id":"feed/http://nothe.purplellamas.net/index.xml","title":"Blog Posts About Stuff","updated":1512518400000,"velocity":0.0,"subscribers":1,"website":"http://nothe.purplellamas.net/","language":"en","state":"dormant","description":"Recent content on Blog Posts About Stuff"},{"feedId":"feed/http://www.mostgood.net/blog?format=RSS","id":"feed/http://www.mostgood.net/blog?format=RSS","title":"mostgood","updated":1433114183000,"velocity":0.0,"subscribers":1,"website":"http://www.mostgood.net/","language":"en","state":"dormant"},{"feedId":"feed/http://www.mistys-internet.website/blog/atom.xml","id":"feed/http://www.mistys-internet.website/blog/atom.xml","title":"The Future Is Now","updated":1550722972000,"velocity":0.0,"subscribers":1,"website":"http://mistys-internet.website/blog/","language":"en","state":"dormant"},{"feedId":"feed/http://pewpewthespells.com/feed.xml","id":"feed/http://pewpewthespells.com/feed.xml","title":"Samantha Marshall's Blog","updated":1569829870614,"velocity":8.6,"subscribers":1,"website":"https://pewpewthespells.com/","language":"en","description":"Blog Feed"},{"feedId":"feed/http://blog.ashleynh.me/rss/","id":"feed/http://blog.ashleynh.me/rss/","title":"Ashley Nelson-Hornstein","updated":1496606482000,"velocity":0.0,"subscribers":1,"website":"http://ashleynh.me:80/","language":"en","state":"dormant","description":"Ashley Nelson"},{"feedId":"feed/https://medium.com/feed/@nerdonica","id":"feed/https://medium.com/feed/@nerdonica","title":"Veronica Ray on Medium","updated":1471184706000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@nerdonica?source=rss-eaf18ccd367f------2","language":"en","state":"dormant","description":"Stories by Veronica Ray on Medium"},{"feedId":"feed/http://www.nadynerichmond.com/blog/feed/","id":"feed/http://www.nadynerichmond.com/blog/feed/","title":"go ahead, mac my day","updated":1506105463000,"velocity":0.0,"subscribers":1,"website":"http://www.nadynerichmond.com/blog","language":"en","state":"dormant","description":"a Macintosh girl in a Microsoft world"},{"feedId":"feed/http://www.bbc.co.uk/blogs/doctorwho/rss","id":"feed/http://www.bbc.co.uk/blogs/doctorwho/rss","title":"Doctor Who","updated":1567440000000,"velocity":0.2,"subscribers":1,"website":"https://www.bbc.co.uk/blogs/doctorwho","language":"en","description":"All the latest news and features from the world of Doctor Who."},{"feedId":"feed/http://scripting.com/rss.xml","id":"feed/http://scripting.com/rss.xml","title":"Scripting News","updated":1569858817428,"velocity":12.6,"subscribers":1,"website":"http://scripting.com/","language":"en","description":"Scripting News, the weblog started in 1994 that bootstrapped the blogging revolution. 🚀"},{"feedId":"feed/http://daringfireball.net/feeds/main","id":"feed/http://daringfireball.net/feeds/main","title":"Daring Fireball","updated":1569916578798,"velocity":12.4,"subscribers":1,"website":"https://daringfireball.net/","language":"en","description":"By John Gruber"},{"feedId":"feed/http://www.mechanicalgirl.com/feeds/all/","id":"feed/http://www.mechanicalgirl.com/feeds/all/","title":"MechanicalGirl","updated":1569829801167,"velocity":1.1,"subscribers":1,"website":"http://www.MechanicalGirl.com/","language":"en","description":"Latest posts on MechanicalGirl"},{"feedId":"feed/http://ranchero.com/xml/rss.xml","id":"feed/http://ranchero.com/xml/rss.xml","title":"ranchero.com","updated":1569437386000,"velocity":1.6,"subscribers":1,"website":"https://inessential.com/","language":"en","description":"Brent Simmons’s weblog."},{"feedId":"feed/http://natashatherobot.com/feed/","id":"feed/http://natashatherobot.com/feed/","title":"Natasha The Robot","updated":1569829929101,"velocity":0.2,"subscribers":2,"website":"https://www.natashatherobot.com","language":"en"},{"feedId":"feed/http://daringfireball.net/index.xml","id":"feed/http://daringfireball.net/index.xml","title":"Daring Fireball","updated":1569897703184,"velocity":14.0,"subscribers":6,"website":"https://daringfireball.net/","language":"en","description":"By John Gruber"},{"feedId":"feed/http://timekl.com/atom.xml","id":"feed/http://timekl.com/atom.xml","title":"don't panic","updated":1555225200000,"velocity":0.0,"subscribers":1,"website":"https://timekl.com/","language":"en","description":"Occasional posts, usually about technology"},{"feedId":"feed/http://nataliepo.typepad.com/nataliepo/rss.xml","id":"feed/http://nataliepo.typepad.com/nataliepo/rss.xml","title":"nataliepo (posts on 'nataliepo' (rss 2.0))","updated":1447256454000,"velocity":0.0,"subscribers":1,"website":"https://nataliepo.typepad.com/nataliepo/","language":"en","state":"dormant"},{"feedId":"feed/http://shapeof.com/rss.xml","id":"feed/http://shapeof.com/rss.xml","title":"The Shape of Everything","updated":1569263479000,"velocity":1.6,"subscribers":1,"website":"https://shapeof.com/","language":"en","description":"A website mostly about Mac stuff, written by Gus Mueller"},{"feedId":"feed/http://jvns.ca/atom.xml","id":"feed/http://jvns.ca/atom.xml","title":"Julia Evans","updated":1569869414502,"velocity":4.1,"subscribers":2,"website":"http://jvns.ca","language":"en"},{"feedId":"feed/https://www.natashatherobot.com/feed/","id":"feed/https://www.natashatherobot.com/feed/","title":"Natasha the Robot","updated":1545498499000,"velocity":0.0,"subscribers":1,"website":"https://www.natashatherobot.com","language":"en","state":"dormant"},{"feedId":"feed/http://pointersgonewild.com/feed/","id":"feed/http://pointersgonewild.com/feed/","title":"Pointers Gone Wild","updated":1560168135000,"velocity":0.1,"subscribers":1,"website":"https://pointersgonewild.com","language":"en","description":"A blog about compilers, programming and technology."},{"feedId":"feed/http://www.kristinathai.com/feed/","id":"feed/http://www.kristinathai.com/feed/","title":"kristinathai.com","updated":1563831802000,"velocity":0.1,"subscribers":1,"website":"http://www.kristinathai.com","language":"ja"},{"feedId":"feed/https://developer.apple.com/swift/blog/news.rss","id":"feed/https://developer.apple.com/swift/blog/news.rss","title":"Swift Blog - Apple Developer","updated":1476306000000,"velocity":0.0,"subscribers":1,"website":"https://developer.apple.com/swift/blog/","language":"en","state":"dormant","description":"Get the latest news and helpful tips on the Swift programming language from the engineers who created it."},{"feedId":"feed/http://www.rebeccamiller-webster.com/feed/","id":"feed/http://www.rebeccamiller-webster.com/feed/","title":"Rebecca Miller-Webster","updated":1547836736000,"velocity":0.0,"subscribers":1,"website":"https://www.rebeccamiller-webster.com","language":"en","state":"dormant","description":"Ruby + JavaScript"},{"feedId":"feed/http://swift.ayaka.me/posts?format=RSS","id":"feed/http://swift.ayaka.me/posts?format=RSS","title":"Learn Swift ↯","updated":1466314290000,"velocity":0.0,"subscribers":1,"website":"http://swift.ayaka.me/","language":"en","state":"dormant"},{"feedId":"feed/http://www.imore.com/rss.xml","id":"feed/http://www.imore.com/rss.xml","title":"iMore","updated":1569893162390,"velocity":6.8,"subscribers":1,"website":"https://www.imore.com/","language":"en","description":"More news and rumors, more help and how-tos, more app and accessory reviews, more iPhone and iPad and iPod touch. More of everything you love. iMore."},{"feedId":"feed/http://blog.thoughtbrain.com/feed/","id":"feed/http://blog.thoughtbrain.com/feed/","title":"Feed: Thoughtbrain Bloggers","updated":1426140251000,"velocity":0.0,"subscribers":1,"website":"http://blog.thoughtbrain.com","language":"en","state":"dormant","description":"Designery nerdy things."},{"feedId":"feed/http://blog.ellenchisa.com/feed/","id":"feed/http://blog.ellenchisa.com/feed/","title":"Ellen's Blog","updated":1546640740000,"velocity":0.0,"subscribers":1,"website":"https://blog.ellenchisa.com?source=rss----da542b929da2---4","language":"en","state":"dormant","description":"I’m starting a new company with @paulbiggar, which you can learn about at https://darklang.com. I mostly write about startups and software development. - Medium"},{"feedId":"feed/https://medium.com/feed/@jaimeejaimee","id":"feed/https://medium.com/feed/@jaimeejaimee","title":"jaimeejaimee","updated":1558376026000,"velocity":0.1,"subscribers":1,"website":"https://medium.com/@jaimeejaimee?source=rss-11d5cc4494a2------2","language":"en","description":"Stories by jaimeejaimee on Medium"}],"label":"Uncategorized","created":1569829699432,"enterprise":false,"numFeeds":55,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8"},{"customizable":true,"feeds":[{"feedId":"feed/http://bryan.io/rss","id":"feed/http://bryan.io/rss","title":"bryan i/o","updated":1567230243000,"velocity":0.2,"subscribers":1,"website":"https://bryan.io/","language":"en","description":"Software engineer who led iOS at Tumblr from 2012-2015. Mostly cheeseburgers at this point. Over at irace.me nowadays."},{"feedId":"feed/http://nickbradbury.com/feed/","id":"feed/http://nickbradbury.com/feed/","title":"Nick Bradbury","updated":1503946502000,"velocity":0.0,"subscribers":1,"website":"https://nickbradbury.com","language":"en","state":"dormant","description":"I develop Android apps. In a previous life I created HomeSite, TopStyle and FeedDemon for Windows."},{"feedId":"feed/http://feeds.feedburner.com/domainofthebored","id":"feed/http://feeds.feedburner.com/domainofthebored","title":"Peter Hosey","updated":1567916255000,"velocity":0.2,"subscribers":1,"website":"https://boredzo.org/blog","language":"en","description":"The personal weblog of Peter Hosey."},{"feedId":"feed/http://blog.metaobject.com/feeds/posts/default","id":"feed/http://blog.metaobject.com/feeds/posts/default","title":"metablog","updated":1556396880001,"velocity":0.0,"subscribers":1,"website":"https://blog.metaobject.com/","language":"en"},{"feedId":"feed/http://feeds2.feedburner.com/adobe/jnack","id":"feed/http://feeds2.feedburner.com/adobe/jnack","title":"John Nack on Adobe (rss (feedburner))","updated":1391820146000,"velocity":0.0,"subscribers":1,"website":"http://blogs.adobe.com/jnack","language":"en","state":"dormant"},{"feedId":"feed/http://www.zathras.de/angelweb/BlogRSSFeed.rss","id":"feed/http://www.zathras.de/angelweb/BlogRSSFeed.rss","title":"Zathras.de - Uli's most useless blog in the World","updated":1562364000000,"velocity":0.1,"subscribers":1,"website":"https://orangejuiceliberationfront.com/","language":"en","description":"Uli's blog on programming, game development, pop culture and other boring things."},{"feedId":"feed/http://typesetinthefuture.com/feed/","id":"feed/http://typesetinthefuture.com/feed/","title":"Typeset In The Future","updated":1544536375000,"velocity":0.0,"subscribers":1,"website":"https://typesetinthefuture.com","language":"en","state":"dormant","description":"Typography and Design in Science Fiction Movies"},{"feedId":"feed/http://david-smith.org/atom.xml","id":"feed/http://david-smith.org/atom.xml","title":"David Smith","updated":1569830048565,"velocity":0.5,"subscribers":1,"website":"http://david-smith.org/","language":"en"},{"feedId":"feed/http://awkwardhare.com/rss","id":"feed/http://awkwardhare.com/rss","title":"Awkward Hare","updated":1484149569000,"velocity":0.0,"subscribers":1,"website":"https://awkwardhare.com/","language":"en","state":"dormant","description":"A blog by Greg Pierce"},{"feedId":"feed/http://frozendevil.com/atom.xml","id":"feed/http://frozendevil.com/atom.xml","title":"frozendevil","updated":1398927600000,"velocity":0.0,"subscribers":1,"website":"http://frozendevil.com/","language":"en","state":"dormant"},{"feedId":"feed/http://useyourloaf.com/blog/rss.xml","id":"feed/http://useyourloaf.com/blog/rss.xml","title":"Use Your Loaf","updated":1569830765949,"velocity":0.5,"subscribers":2,"website":"https://useyourloaf.com/blog/","language":"en","description":"Recent content on Use Your Loaf - iOS Development News & Tips"},{"feedId":"feed/http://www.appleoutsider.com/feed/","id":"feed/http://www.appleoutsider.com/feed/","title":"Apple Outsider","updated":1402413759000,"velocity":0.0,"subscribers":1,"website":"https://www.appleoutsider.com","language":"en","state":"dormant"},{"feedId":"feed/http://rathole.tumblr.com/rss","id":"feed/http://rathole.tumblr.com/rss","title":"RatHole","updated":1554476924000,"velocity":0.0,"subscribers":1,"website":"https://rathole.tumblr.com/","language":"en","description":"what my brain does when I’m not looking"},{"feedId":"feed/http://codeplease.io/rss/","id":"feed/http://codeplease.io/rss/","title":"Codeplease","updated":1511101649000,"velocity":0.2,"subscribers":1,"website":"http://codeplease.io/","language":"en","state":"dead","description":"Ramblings about code"},{"feedId":"feed/http://blog.jaredsinclair.com/rss?1","id":"feed/http://blog.jaredsinclair.com/rss?1","title":"Jared Sinclair","updated":1554679244000,"velocity":0.0,"subscribers":1,"website":"https://jaredsinclair.com/","language":"en","description":"Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description."},{"feedId":"feed/http://www.takingnotes.co/atom.xml","id":"feed/http://www.takingnotes.co/atom.xml","title":"Doug Russell","updated":1541721600000,"velocity":0.0,"subscribers":1,"website":"http://takingnotes.co//","language":"en","state":"dormant"},{"feedId":"feed/http://www.red-sweater.com/blog/feed","id":"feed/http://www.red-sweater.com/blog/feed","title":"Red Sweater","updated":1568998271000,"velocity":0.5,"subscribers":1,"website":"https://red-sweater.com/blog","language":"en","description":"Official blog of Red Sweater Software"},{"feedId":"feed/http://mjtsai.com/blog/feed/","id":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","updated":1569877811291,"velocity":18.3,"subscribers":1,"website":"https://mjtsai.com/blog","language":"en"},{"feedId":"feed/http://jnack.com/blog/?feed=rss2","id":"feed/http://jnack.com/blog/?feed=rss2","title":"Nackblog","updated":1569833721528,"velocity":2.5,"subscribers":1,"website":"http://jnack.com/blog","language":"en","description":"Musings on photography, illustration, mobile apps, and more"},{"feedId":"feed/http://bitsplitting.org/feed/","id":"feed/http://bitsplitting.org/feed/","title":"Daniel Jalkut","updated":1563906209098,"velocity":0.1,"subscribers":1,"website":"https://bitsplitting.org","language":"en","description":"Chasing the impossible with Daniel Jalkut"},{"feedId":"feed/http://stmts.net/feed/","id":"feed/http://stmts.net/feed/","title":"Jesper","updated":1298233430000,"velocity":0.0,"subscribers":1,"website":"http://stmts.net","language":"en","state":"dormant","description":"On programming"},{"feedId":"feed/http://www.mikeash.com/pyblog/rss.py","id":"feed/http://www.mikeash.com/pyblog/rss.py","title":"NSBlog","updated":1530280470876,"velocity":0.0,"subscribers":2,"website":"http://www.mikeash.com/pyblog/","language":"en","state":"dormant","description":"Mac OS X and Cocoa programming"},{"feedId":"feed/http://corporationunknown.com/blog/feed/","id":"feed/http://corporationunknown.com/blog/feed/","title":"Corporation Unknown","updated":1420427842000,"velocity":0.0,"subscribers":1,"website":"http://corporationunknown.com/blog","language":"en","state":"dormant"},{"feedId":"feed/http://jamesdempsey.net/feed/","id":"feed/http://jamesdempsey.net/feed/","title":"James Dempsey","updated":1568304414000,"velocity":0.2,"subscribers":1,"website":"https://jamesdempsey.net","language":"en","description":"From Apple to Indie in three easy steps"},{"feedId":"feed/http://brian-webster.tumblr.com/rss","id":"feed/http://brian-webster.tumblr.com/rss","title":"Very Web. Such Blog. Wow.","updated":1530051926000,"velocity":0.0,"subscribers":1,"website":"https://brian-webster.tumblr.com/","language":"en","state":"dormant","description":"Brian Webster’s sporadic blogging about mostly programming stuff."},{"feedId":"feed/http://dangillmor.com/feed/","id":"feed/http://dangillmor.com/feed/","title":"Dan Gillmor","updated":1565895640000,"velocity":0.2,"subscribers":1,"website":"http://dangillmor.com","language":"en","description":"Just in case you were still wondering…"},{"feedId":"feed/http://www.jeffmcleman.com/blog/feed/","id":"feed/http://www.jeffmcleman.com/blog/feed/","title":"Jeff McLeman","updated":1563565996000,"velocity":0.1,"subscribers":1,"website":"https://www.jeffmcleman.com/blog","language":"en","description":"The Brooding Thoughts of an Untamed Mind"},{"feedId":"feed/http://www.caseyliss.com/rss","id":"feed/http://www.caseyliss.com/rss","title":"Liss is More","updated":1569830046093,"velocity":0.5,"subscribers":1,"website":"https://www.caseyliss.com","language":"en","description":"Posts to Liss is More"},{"feedId":"feed/http://ignorethecode.net/blog/rss/","id":"feed/http://ignorethecode.net/blog/rss/","title":"ignorethecode.net","updated":1532170394000,"velocity":0.0,"subscribers":1,"website":"http://ignorethecode.net","language":"en","state":"dormant","description":"Essays on usability, programming, and other nerd topics."},{"feedId":"feed/http://sheilasweblog.wordpress.com/feed/","id":"feed/http://sheilasweblog.wordpress.com/feed/","title":"Sheila's Weblog","updated":1237602766000,"velocity":0.0,"subscribers":1,"website":"https://sheilasweblog.wordpress.com","language":"en","state":"dormant","description":"Quilting, kitties, other fun stuff."},{"feedId":"feed/http://www.allenpike.com/feed/","id":"feed/http://www.allenpike.com/feed/","title":"Allen Pike","updated":1569829990142,"velocity":0.2,"subscribers":1,"website":"https://www.allenpike.com/","language":"en"},{"feedId":"feed/https://developer.apple.com/news/rss/news.rss","id":"feed/https://developer.apple.com/news/rss/news.rss","title":"iPhone Developer News","updated":1569869696571,"velocity":3.2,"subscribers":1,"website":"https://developer.apple.com/news/","language":"en","description":"Apple Developer News and Updates feed provided by Apple, Inc."},{"feedId":"feed/http://themainthread.com/feed.xml","id":"feed/http://themainthread.com/feed.xml","title":"The Main Thread","updated":1440820800000,"velocity":0.0,"subscribers":1,"website":"http://themainthread.com/","language":"en","state":"dormant"},{"feedId":"feed/http://www.gordonmeyer.com/atom.xml","id":"feed/http://www.gordonmeyer.com/atom.xml","title":"Gordon Meyer (posts on 'gordon meyer' (atom))","updated":1569066494000,"velocity":0.5,"subscribers":1,"website":"https://www.gordonmeyer.com/","language":"en"},{"feedId":"feed/http://www.mondaynote.com/feed/","id":"feed/http://www.mondaynote.com/feed/","title":"Monday Note","updated":1569786821000,"velocity":2.0,"subscribers":1,"website":"https://mondaynote.com?source=rss----c537d80ed0a---4","language":"en","description":"Media, Tech, Business Models viewed from Palo Alto and Paris - Medium"},{"feedId":"feed/http://www.randsinrepose.com/index.xml","id":"feed/http://www.randsinrepose.com/index.xml","title":"Rands In Repose","updated":1569830107339,"velocity":0.2,"subscribers":4,"website":"https://randsinrepose.com","language":"en"},{"feedId":"feed/http://furbo.org/feed/","id":"feed/http://furbo.org/feed/","title":"furbo.org","updated":1569830050733,"velocity":0.5,"subscribers":1,"website":"https://furbo.org","language":"en","description":"by Craig Hockenberry"},{"feedId":"feed/http://feeds.feedburner.com/NSHipster","id":"feed/http://feeds.feedburner.com/NSHipster","title":"NSHipster","updated":1569222000000,"velocity":0.7,"subscribers":1,"website":"https://nshipster.com/","language":"en","description":"NSHipster is a journal of the overlooked bits in Objective-C, Swift, and Cocoa. Updated weekly."},{"feedId":"feed/http://www.neglectedpotential.com/feed/","id":"feed/http://www.neglectedpotential.com/feed/","title":"Neglected Potential","updated":1537811051000,"velocity":0.0,"subscribers":1,"website":"http://www.neglectedpotential.com","language":"en","state":"dormant"}],"label":"Weblogs","created":1569829952574,"enterprise":false,"numFeeds":39,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366"}] \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/AddCollection/newcollection_addcollection.json b/Frameworks/Account/AccountTests/Feedly/AddCollection/newcollection_addcollection.json new file mode 100644 index 000000000..918b7ef56 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/AddCollection/newcollection_addcollection.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","updated":1569829821629,"items":[{"originId":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","fingerprint":"efab5851","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d996:18991ffa","summary":{"direction":"ltr","content":"

I had the fun of interviewing old friend Daniel Jalkut on the latest episode of The Omni Show.

"},"alternate":[{"href":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","type":"text/html"}],"crawled":1569829821629,"published":1569437386000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","fingerprint":"f53acc86","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d995:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0.1 is almost entirely a bug-fix release — see the release notes for the full scoop.

\n

It includes one sort-of new feature: there’s now a checkbox in Preferences for turning off the unread count in the Dock. (It was a hidden pref — now it’s visible.)

\n

Status

\n

Here’s what else we’re working on:

\n
    \n
  • iOS/iPadOS app
  • \n
  • NetNewsWire 5.0.2 for Mac — which will mainly be about performance (yes, we can make it even faster)
  • \n
  • NetNewsWire 5.1 for Mac — tentative feature list includes content extraction and at least one more syncing option (but we might change our minds on these: anything can happen between now and then)
  • \n
\n

We might also distribute NetNewsWire 5.0.2 for Mac on the Mac App Store. No guarantees yet, of course, but work is happening in that direction. This goes to our goal of getting as many people as possible using RSS readers.

"},"alternate":[{"href":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0.1 Released","published":1568408217000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","fingerprint":"5b6c292f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d994:18991ffa","summary":{"direction":"ltr","content":"

Had to get a new key fob at work today — my old one wore out. Just a couple weeks shy of my fifth anniversary at Omni! Time flies.

\n

I figure I’m just over eight years from retiring, so I’m not even halfway done here. :)

"},"alternate":[{"href":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","type":"text/html"}],"crawled":1569829821629,"published":1568153137000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","fingerprint":"3b5ade1b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d993:18991ffa","summary":{"direction":"ltr","content":"

People have been asking me about supporting iCloud as a sync method for NetNewsWire.

\n

It would be really cool because:

\n
    \n
  • There’s no sign-in
  • \n
  • It’s free — no need to spend money on another service
  • \n
  • It would help broaden the pool of people using RSS, since there would be no additional expense or service they’d need — they could just get going
  • \n
\n

It’s a great idea — no question. Given that my goal is to get as many people as possible using RSS, this makes total sense.

\n

Why we didn’t ship with this feature

\n

For the first release — I still think of it as a 1.0, because it really is — our best bet was to appeal to people already using an existing RSS service. We know that those people like and use RSS, and they’re the people most likely to check out a new RSS app.

\n

(We could have delayed and shipped with support for more existing services, but we figured one was enough to get started with, and we could add other services later. And we are.)

\n

In other words, we tried to make an app that the existing market would like. And that’s the right call when you’re starting out.

\n

Also: iCloud sync makes the most sense when you have both a Mac and an iOS app, and we don’t — the iOS app is still in progress. We totally expect people to use NetNewsWire on the Mac and Unread or Reeder on their iPhone and iPad — and iCloud sync won’t work across apps. This scenario requires using services such as Feedbin.

\n

Why I have no idea when this feature might appear

\n

For any existing RSS service, we can be confident that our effort to support it in NetNewsWire would be successful. This is well-trodden ground: we make some web API calls, integrate with our database, and done. It’s not nothing, but conceptually it’s simple and there’s no cause to worry about technical issues.

\n

But iCloud syncing will mean writing exploratory code and only then finding out if it’s going to work.

\n

Syncing the feeds list should be relatively easy — the real issue is with syncing read/unread/starred states of articles. That means a lot of small records.

\n

Is CloudKit up to this? What are the limits? How fast is it? How reliable?

\n

We just don’t know.

\n

Yes, it’s encouraging that News Explorer has this feature — but that doesn’t tell us much about the limits, reliability, and performance.

\n

Working on this is a risk.

\n

So — as you can imagine — we’re still more keen on supporting existing RSS services, because we know there are plenty of people who for-sure like RSS, and who might like NetNewsWire, but who won’t switch their syncing system just to use NetNewsWire.

\n

That said: I do think we’ll get around to trying this, and I’ll be super-pleased if it works, because it really is a great idea — but we have a bunch of other work to do first. (Including the iOS app!)

"},"alternate":[{"href":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","type":"text/html"}],"crawled":1569829821629,"title":"On Syncing NetNewsWire Using iCloud","published":1567817061000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","fingerprint":"ca200f5a","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d992:18991ffa","summary":{"direction":"ltr","content":"

Markos Charatzas writes about his excitement in joining the Apple developer world in 2009 to his eventual disillusionment today.

"},"alternate":[{"href":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","type":"text/html"}],"crawled":1569829821629,"published":1567788970000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","fingerprint":"66196df9","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d991:18991ffa","summary":{"direction":"ltr","content":"

A number of people have asked that NetNewsWire show the full web page — right there, in the app — after clicking a link.

\n

The idea is pretty good! It solves two big problems:

\n
    \n
  • You get full content, which is great when a feed contains only summaries or truncated articles
  • \n
  • You don’t have to switch to another app: you can stay right where you are
  • \n
\n

You’d think it’s a no-brainer, and we should just go ahead. But there are other considerations.

\n

One big one is that your ad blockers and privacy extensions won’t run. They work in Safari, but they do not extend to other apps that use WebKit. This means that viewing a web page in NetNewsWire would be less secure and more annoying than viewing the same page in Safari (or whatever your browser is).

\n

This points to one of my design principles: the app should have boundaries. Some features belong in the app, and some features are best left to apps that do that feature way better than NetNewsWire could. One of those things is showing web pages — that’s really a web browser feature.

\n

Having boundaries means we can concentrate on doing a great job at the things that do belong in the app.

\n

(Before you mention SFSafariViewController, recall that it’s iOS-only.)

\n

What about the glory days?

\n

“But Brent! In NetNewsWire 2.0 you added a tabbed browser to NetNewsWire, and it was awesome and a hugely popular feature!”

\n

It was! But times have changed. Many websites are hostile these days. In 2005, this feature was fine — but these days it’s totally not.

\n

A winged messenger arrives with a solution

\n

There is a solution to the problem of showing full content and not leaving the app, and it’s a feature that really does belong in an RSS reader: using content extraction to grab the article from the original page.

\n

If you’ve ever used Safari’s Reader view, then you know what I’m talking about. The idea is that NetNewsWire would do something very much like the Reader view (but inline, in the article pane), that grabs the content and formats it nicely, without all the extra junk that is not the article you want to read.

\n

There are a number of open source options for this. We’re looking at using Feedbin’s content extraction service (which wouldn’t require you to have a Feedbin account).

\n

The generous folks at Feedbin are running a copy of the open-source Mercury Parser, and they’ve offered to open this service up to RSS readers like NetNewsWire. (Reeder uses it already, for instance.)

\n

When?

\n

Right now we’re working on NetNewsWire 5.0.1, which is (almost entirely) a bug-fix release. I don’t know what’s going to be in 5.1 yet — we’re still digesting all the feedback, looking at our original roadmap, and thinking about things.

\n

We’re also working on NetNewsWire for iOS! We’re busy.

\n

But this is definitely the kind of feature that should come sooner rather than later.

"},"alternate":[{"href":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"On the Many NetNewsWire Feature Requests to Show Full Web Pages","published":1567661107000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","fingerprint":"bb260103","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d990:18991ffa","summary":{"direction":"ltr","content":"

John Gruber has mentioned, on The Talk Show, that I’ve got some weird ideas about what beta means.

\n

Here are my definitions:

\n

development (d): everything is in progress and the app might be completely unusable.

\n

alpha (a): the app is feature-complete and has no known bugs — but, importantly, it’s had very little testing.

\n

beta (b): the app is feature-complete, has no known bugs, and has been tested — but further testing is still warranted. Every beta is a release candidate.

\n

These are defined in a NetNewsWire Technote. It’s important to have definitions that everybody working on or testing the app understands.

\n

But why these rather strict definitions?

\n

It’s part of our commitment to quality. What matters is the end result — the shipping app — and these definitions make sure we don’t get to beta, or even alpha, with the app up on the table with wires sticking out and pieces missing.

\n

This gives us a big space between development and shipping, and that space is all about making sure the bugs are all fixed.

\n

This is a matter of ethics and pride in our work. Absolutely.

\n

But it’s also pragmatic. This is an open source app, written by volunteers in their spare time, and having this rhythm baked-in to the process helps make sure we can uphold our standards even without full-time developers, managers, and testers.

\n

* * *

\n

And… it bugs me how little real attention our industry pays to quality these days. In some cases the consequences are disastrous; in other cases they’re merely expensive. It doesn’t have to be this way.

\n

If it seems like I’m going too far with my definitions, well, I’m trying to bend the stick here.

"},"alternate":[{"href":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","type":"text/html"}],"crawled":1569829821629,"title":"On My Funny Ideas About What Beta Means","published":1567455823000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","fingerprint":"e526eb19","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98f:18991ffa","summary":{"direction":"ltr","content":"

I love this NetNewsWire write-up on WP Tavern.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","type":"text/html"}],"crawled":1569829821629,"published":1567280353000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","fingerprint":"57ea983b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98e:18991ffa","summary":{"direction":"ltr","content":"

NetNewsWire 5.0 is a 1.0 app in disguise.

\n

And so, as expected, we’ve had a ton of feature requests. Most people tend to request one or two features — and there’s a huge variety in these. People want different things.

\n

Nevertheless, there are a few themes we can pick out from what people are asking for:

\n
    \n
  • More syncing options, especially Feedly support
  • \n
  • iOS app
  • \n
  • Some way to deal with partial-content feeds
  • \n
  • Customization of the article pane (fonts, colors, etc.)
  • \n
  • Traditional view (timeline on top with single lines, article below)
  • \n
  • More sharing options (Instapaper, Pinboard, etc.)
  • \n
  • Customizable keyboard shortcuts
  • \n
  • State restoration
  • \n
  • Localizations
  • \n
  • Hiding read items in the timeline (or dimming them)
  • \n
  • Hiding feeds (in the sidebar) that have no unread articles
  • \n
  • User-created smart feeds
  • \n
\n

The less-common, more singular requests are for things like specific sorting options — there are lots of different small options that people would like.

\n

People have also asked for things that might surprise you (they surprised me) — for instance, we’ve had a request for monochrome icons for the toolbar. Another request for a Dark Mode that’s different from Apple’s Dark Mode. Etc.

\n

How We Choose What To Do Next

\n

The first principle is that we can’t lose what we love about the app. We do our damnedest to ship with no bugs, and the app needs to be fast and, most importantly, it needs to feel lighter-than-air.

\n

Whenever you add things — even if the app remains just as fast, even if there are no bugs — you still run the risk of losing that feeling of lightness. One of the quickest ways to lose that feeling is to add a whole bunch of preferences, View menu options, toolbar commands, and other chrome. So we’re going to be very slow to add things like that.

\n

NetNewsWire needs to not become fiddly. (Earlier versions of NetNewsWire got way too fiddly.)

\n

There are other questions we ask about a feature before we do it.

\n
    \n
  • Will it substantially benefit current users?
  • \n
  • Will it bring a number of new users to the app?
  • \n
  • Does the feature depend on something else being done first?
  • \n
  • How much work will it take?
  • \n
  • Does it require resources (such as new icons) that our programmers can’t provide?
  • \n
  • Does the feature really belong in an RSS reader at all?
  • \n
\n

And, because this is an open source app, there’s another dimension: people. Is someone available? Has someone just shown up who’s eager to work on a specific feature? Those things have an impact on scheduling, too.

\n

The good news is that most of the common feature requests are obvious things to do.

\n

Some examples — not nearly everything, just a few thoughts:

\n

The iOS app is in progress. Maurice Parker has been writing it, and it’s coming along very well. Still plenty more to do, and we won’t ship before iOS 13 ships, but it’s happening.

\n

Adding syncing options is a definite good thing for the app. Doing the first one (Feedbin) was the big effort, because it required building the infrastructure that makes syncing possible. Once that was done, adding additional services is not super-difficult. (Not easy, no. Nothing’s trivial. But at least the infrastructure and patterns are in place.)

\n

We’d like to support all the various services, or at least a majority of them. And we have people working on adding services.

\n

Customization of the article pane will most likely work the way it did in older versions of NetNewsWire: we had theme files which included templates and CSS. The app shipped with a few, and you could make your own and use themes other people made.

\n

This feature shipped with NetNewsWire 2.0, and people really loved it. It was fun!

\n

More sharing options is an obvious good idea. Of course you should be able to send to Instapaper, Pocket, Pinboard, and so on. We shipped with custom support for MarsEdit and the Micro.blog app — mainly because I use those apps. But an RSS reader ought to support as many sharing workflows as possible. That’s one of the core points of the app.

\n

* * *

\n

Anyway — the above doesn’t cover everything. Don’t take any of the above as gospel about what we’re doing or when, or what we’re not doing. We haven’t planned 5.1 yet! It’s too soon.

\n

There are also features that we want to do that people haven’t asked for, but that we think are cool. \uD83C\uDFB8

\n

The take-away from this article should be: we’re being very careful about designing and implementing new features, because we have to make sure NetNewsWire doesn’t lose what makes it special.

\n

But we are doing new features, because there are so many things that can make the app even better — we can make it better for current users and we can bring in new users.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5 Feature Requests","published":1567278518000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/29/follow_through","fingerprint":"444937b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98d:18991ffa","summary":{"direction":"ltr","content":"

Decades ago, when I was working for Dave Winer at UserLand, I learned about the concept of follow-through after a major release.

\n

If you’re an app maker, it might seem like your goal is to get to release day. Get the app done, make it available, publish an announcement, and then get back to coding. Let the world do what it’s going to do.

\n

One bang, and then back to work, in other words.

\n

But that’s not going to maximize your chances for a good release. You need to follow through — you need to keep going.

\n

Some of the things you might do, in no particular order:

\n
    \n
  • Publish tips on using your app — one a day or so
  • \n
  • Update your website with feedback, testimonials, and good reviews
  • \n
  • Be available and communicative about your app
  • \n
  • Go on some podcasts
  • \n
  • Write about how release day went
  • \n
  • Write about plans for the x.0.1 version
  • \n
  • Field bug reports and feature requests gratefully
  • \n
  • Thank reviewers who’ve done a good job
  • \n
  • Make it as easy as possible for reporters and reviewers to get access to your app and to you
  • \n
  • Work to build a community of customers, on Slack or similar
  • \n
\n

I’m sure you can think of more things to do — the above isn’t everything, and every app is different.

\n

But the key is that you don’t just do the release and then stop. Instead, show that you‘re responsive, show that your app has momentum, show that you care enough to keep showing up.

\n

For me, at least, this is the fun part. I realize that’s not true for everybody — but you should do it anyway. \uD83C\uDFA9

"},"alternate":[{"href":"https://inessential.com/2019/08/29/follow_through","type":"text/html"}],"crawled":1569829821629,"title":"Follow-Through","published":1567110304000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","fingerprint":"669e65c4","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98c:18991ffa","summary":{"direction":"ltr","content":"

We have a few reports of a crash where the add-feed-sheet window doesn’t load. There’s a line of code with window! — because of course we expect the window to have been loaded — and it crashes right there.

\n

This crash made zero sense to me, but Daniel Jalkut figured out the most likely cause and was able to reproduce it: it’s because the person has moved the app (from one folder to another) after launching it, while it’s running, and the nib-loading machinery can’t find the nib, because it’s moved along with the app.

\n

Tip: if you’re going to move an app, quit it first, then move it, and then re-launch it!

\n

At any rate: our fix for this will be to load that sheet on startup, and then recycle it on each use. This fix will go into NetNewsWire 5.0.1.

\n

This just fixes the bug with this one nib, though. A more systematic fix — maybe just a warning to the user suggesting they quit and re-launch — would be a good idea.

\n

File under “bugs iOS developers never have to worry about.” \uD83D\uDC07

\n

PS We have a 5.0.1 beta milestone now.

"},"alternate":[{"href":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","type":"text/html"}],"crawled":1569829821629,"title":"Daniel Figures Out One of the Two Crashing Bugs","published":1567022746000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/27/how_release_day_went","fingerprint":"2394a816","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98b:18991ffa","summary":{"direction":"ltr","content":"

Yesterday was a great day! A few things to note, in no particular order:

\n

NetNewsWire got some press coverage, including a well-done review in MacStories.

\n

We got a lot of feature requests, but no bug reports.

\n

Except that we did get a single-digit number of crash logs. On investigation, I found two distinct backtraces — we’ll need to fix those. The thing is, there’s no freakin’ way the app should crash in those spots. Except that, obviously, it can. Rarely, but it happens.

\n

The servers started timing-out at one point during the day. I contacted DreamHost support and they fixed things (and told me that the fixes they applied should prevent this in the future).

\n

There were a number of nice blog posts and tweets about NetNewsWire, which was awesome. After working so hard for so long, it’s great when people appreciate the app. We don’t get paid in money, after all. \uD83D\uDC23

\n

I have no idea how many downloads of the app there were. GitHub is hosting the download, via its releases feature, and I don’t see a way to find out how many times it’s been downloaded. Which is totally fine with me.

\n

* * *

\n

I should say something more about the no-bug-reports. There’s no special magic or talent or anything to this — there’s just the willingness to say that we’re not going to ship until we’ve got the bugs out, and then sticking to that.

\n

This is a matter of pride and ethics, for sure, but there’s another dimension: since the app is open source, it’s written by volunteers (including me), and we have no dedicated support team. Any time we spend fielding bug reports is time taken away from working on the next feature.

\n

Making apps — even, or especially, free apps — is an exercise in economics. With free apps, the economics are even more constrained, because nobody is going to hire even a part-time support person. So we do everything we can do keep costs down — especially time costs.

\n

Plus — buggy apps can be demoralizing to the people who work on them. Part of my job is to make sure people are proud and happy to work on the app. And that means making sure everyone knows we’re super-serious about doing our best to never ship bugs.

"},"alternate":[{"href":"https://inessential.com/2019/08/27/how_release_day_went","type":"text/html"}],"crawled":1569829821629,"title":"How Release Day Went","published":1566937707000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","fingerprint":"175d2cdb","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98a:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0 is shipping!

\n

In case you haven’t been following along until just now: NetNewsWire is an open source RSS reader for Mac. It’s free! You can just download it and use it. No strings.

\n

It’s designed to be stable, fast, and free of bugs. It doesn’t have a lot of features yet, and that’s because we prioritized quality over features. We will be adding more features, of course, but not quickly. We’re also working on an iOS app.

\n

It syncs using Feedbin. We’ll support more systems in the future (as many as possible).

\n

I hope you like it!

\n

Some links…

\n\n

Thanks to so many people

\n

I want to especially thank Sheila Simmons and my family and friends.

\n

This release took five years to make, and for four of those years it wasn’t even called NetNewsWire. It was just a year ago that I got the name NetNewsWire back from Black Pixel — and I thank them again for their wonderful generosity.

\n

I also want to thank Brad Ellis for making the beautiful app icon and toolbar icons. Thanks to our major code contributors: Maurice Parker, Olof Hellman, and Daniel Jalkut. Thanks to Ryan Dotson for writing the Help book. Thanks to Joe Heck for looking after infrastructure issues (especially continuous integration).

\n

Thanks to my co-workers and friends at The Omni Group (which is a wonderful place to work). Thanks to the ever-patient and ever-awesome NetNewsWire beta testers on the Slack group and elsewhere.

\n

And thanks to everyone who’s ever used the app in its 17-years-and-counting run. Because of you, NetNewsWire has been, and remains, the thrill of my career.

"},"alternate":[{"href":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0 Now Available","published":1566834451000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","fingerprint":"e30daaa8","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d989:18991ffa","summary":{"direction":"ltr","content":"

This is a little bit of bad news. It’s not my intention, and it’s not what I want to happen — but NetNewsWire 3.3.2 apparently does not launch in the next version of macOS (10.15, Catalina).

\n

It links to the PubSub framework, which is not included with the next macOS.

\n

NetNewsWire 3.3.2 was the last release of the full version that I worked on, before selling NetNewsWire to Black Pixel, and I’ve heard from lots of people that they’ve been using it ever since. They never switched.

\n

I would rather it continued working forever, but that’s not to be. Not my choice. Sorry about that!

"},"alternate":[{"href":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","type":"text/html"}],"crawled":1569829821629,"title":"End of the Line for NetNewsWire 3.3.2","published":1566515704000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","fingerprint":"c583e740","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d988:18991ffa","summary":{"direction":"ltr","content":"

The NetNewsWire blog has the details on NetNewsWire 5.0b5 — which should be the last beta.

\n

Still planning to do the 5.0 final release Monday morning, which really means doing the release on Sunday and pushing an announcement to this blog Monday morning. :)

\n

The last things on my to-do list are actually writing that announcement and doing screenshots for the NetNewsWire web page. Easy. \uD83D\uDC2F

"},"alternate":[{"href":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","type":"text/html"}],"crawled":1569829821629,"published":1566452581000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/20/immunization","fingerprint":"39a4bdb0","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d987:18991ffa","summary":{"direction":"ltr","content":"

Before every major release I like to try and think of everything mean that people might say about the app. It’s fun!

\n

So we just went through this exercise on the NetNewsWire Slack group. Here’s a taste:

\n
    \n
  • This took five years? I could write an RSS parser in a weekend.
  • \n
  • Can’t get my Twitter and Facebook feeds. Whatever.
  • \n
  • Doesn’t work with my Usenet host.
  • \n
  • The information density of the timeline is… lacking. What the hell.
  • \n
  • Not truly open source since it’s on a Mac.
  • \n
  • Not truly open source since it’s not GPL.
  • \n
  • No vim keys. Why bother.
  • \n
  • Regular people will never use an RSS reader. What’s the point?
  • \n
  • Brent’s last good idea was in 2002. Consider this a textbook case of coasting.
  • \n
  • Great app. Too bad RSS died with Google Reader.
  • \n
  • It totally didn’t pick up my subscriptions from the earlier version. How is this an upgrade?
  • \n
  • When does a 5.0 have fewer features than a 3.0? When it’s NetNewsWire.
  • \n
  • The echo chamber will love this app. They always do.
  • \n
  • Free app. Continues the race to the bottom. Pour one out for Silvio Rizzi.
  • \n
  • No way to send to Instapaper. Fuck it.
  • \n
  • Brent Simmons can’t stop pursuing a technology that even Google famously admitted was not worth bothering with.
  • \n
  • If this app took five years, imagine how long it will take before it will actually sync with Feedly.
  • \n
  • Sure it’s free, but I bet the Feedbin people paid them off, because the only way to sync is to pay money to Feedbin.
  • \n
  • No iCloud sync? Jerks.
  • \n
  • No iOS app. The revolution happened on mobile, Brant. What the actual fuck.
  • \n
  • Shoulda been Catalyst. Dinosaurs wrote this app.
  • \n
  • Not on the Mac App Store? I guess they don’t want users.
  • \n
  • I would totally use this if it had just this one [feature x], which I can’t believe they shipped without. (Multiply this comment by 100, with a different feature x each time.)
  • \n
  • Area Man Can’t Let RSS Go
  • \n
\n

Some feedback will be factually inaccurate, but we like to imagine that too:

\n
    \n
  • I remember using NetNewsWire on OS 9, and it hasn’t really improved since then. They should make it a Cocoa app.
  • \n
  • Doesn’t work with web comics. POS
  • \n
  • Doesn’t support 10.5.
  • \n
  • It should be free.
  • \n
  • You’d think they would have updated the design — but it looks exactly like NetNewsWire of old.
  • \n
  • Why the hell would they build on that aging code base from Black Pixel? I heard it doesn’t even use ARC.
  • \n
  • No way to sync? What’s their actual problem?
  • \n
\n

See? The actual feedback will be nicer than the stuff we thought up. This provides a bit of immunization. :)

\n

But, also, there will be negative feedback we didn’t imagine. That’s the gold!

\n

* * *

\n

Bonus from Daniel Jalkut, but not actually a criticism:

\n
\n

Can’t innovate, my RSS.

\n
"},"alternate":[{"href":"https://inessential.com/2019/08/20/immunization","type":"text/html"}],"crawled":1569829821629,"title":"Immunization","published":1566332363000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","fingerprint":"592043f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d986:18991ffa","summary":{"direction":"ltr","content":"

I think we’re still on track for releasing NetNewsWire 5.0 Monday, August 26. There will be one more beta before then.

\n

I’ll be available for podcasts, interviews-via-email, etc. If you’d like to set something up, email me or DM me on Twitter.

"},"alternate":[{"href":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","type":"text/html"}],"crawled":1569829821629,"published":1566259329000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/AddFeed/feedly_collections_addfeed.json b/Frameworks/Account/AccountTests/Feedly/AddFeed/feedly_collections_addfeed.json new file mode 100644 index 000000000..bd718c0cb --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/AddFeed/feedly_collections_addfeed.json @@ -0,0 +1 @@ +[{"customizable":true,"feeds":[{"feedId":"feed/http://tidbits.com/feeds/tidbits_blurb.rss","id":"feed/http://tidbits.com/feeds/tidbits_blurb.rss","title":"TidBITS: Apple News for the Rest of Us","updated":1569876918424,"velocity":7.7,"subscribers":1,"website":"https://tidbits.com","language":"en","description":"Thoughtful, detailed coverage of everything Apple for 29 years\nand the TidBITS Content Network for Apple professionals"},{"feedId":"feed/http://www.macalope.com/feed/","id":"feed/http://www.macalope.com/feed/","title":"Macalope","updated":1498941877000,"velocity":0.0,"subscribers":1,"website":"http://www.macalope.com","language":"en","state":"dormant","description":"Full of sound and furry"},{"feedId":"feed/http://flyingmeat.com/blog/atom.xml","id":"feed/http://flyingmeat.com/blog/atom.xml","title":"The Flying Meat Weblog","updated":1343168154000,"velocity":0.0,"subscribers":1,"website":"https://flyingmeat.com/blog/","language":"en","state":"dormant"},{"feedId":"feed/http://www.macdrifter.com/feeds/all.atom.xml","id":"feed/http://www.macdrifter.com/feeds/all.atom.xml","title":"Macdrifter","updated":1561539243000,"velocity":0.1,"subscribers":1,"website":"http://www.macdrifter.com/","language":"en"},{"feedId":"feed/http://9to5mac.com/feed/","id":"feed/http://9to5mac.com/feed/","title":"9to5Mac","updated":1569970519358,"velocity":28.0,"subscribers":2,"website":"https://9to5mac.com","language":"en","description":"Apple News & Mac Rumors Breaking All Day"}],"label":"Macintosh","created":1569829941677,"enterprise":false,"numFeeds":5,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815"},{"customizable":false,"feeds":[{"feedId":"feed/http://inessential.com/xml/rss.xml","id":"feed/http://inessential.com/xml/rss.xml","title":"Inessential","updated":1569971230061,"velocity":4.5,"subscribers":1,"website":"https://inessential.com/","mustRead":true,"language":"en","description":"Brent Simmons’s weblog."}],"label":"Must Read","created":1569417923847,"enterprise":false,"numFeeds":1,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must"},{"customizable":true,"feeds":[{"feedId":"feed/http://inessential.com/xml/rss.xml","id":"feed/http://inessential.com/xml/rss.xml","title":"Inessential","updated":1569971230061,"velocity":4.5,"subscribers":1,"website":"https://inessential.com/","mustRead":true,"language":"en","description":"Brent Simmons’s weblog."}],"label":"NewCollection","created":1569918572642,"enterprise":false,"numFeeds":1,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173"},{"customizable":true,"feeds":[{"feedId":"feed/http://www.codebykevin.com/blosxom.cgi/index.rss","id":"feed/http://www.codebykevin.com/blosxom.cgi/index.rss","title":"Code by Kevin","updated":1564803480000,"velocity":0.1,"subscribers":1,"website":"https://www.codebykevin.com/blosxom.cgi","language":"en","description":"Programming, code, business, and other pursuits"},{"feedId":"feed/http://www.raywenderlich.com/feed","id":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich","updated":1569960327114,"velocity":5.4,"subscribers":1,"language":"en"},{"feedId":"feed/http://ddeville.me/feed.xml","id":"feed/http://ddeville.me/feed.xml","title":"Damien DeVille","updated":1454187600000,"velocity":0.0,"subscribers":1,"website":"http://ddeville.me","language":"en","state":"dormant","description":"Software engineer at Dropbox. Previously at Realmac Software. UCL Computer Science alumnus."},{"feedId":"feed/http://owensd.io/rss.xml","id":"feed/http://owensd.io/rss.xml","title":"owensd.io - thoughts in and out - Articles","updated":1534837458000,"velocity":0.0,"subscribers":1,"website":"https://owensd.io","language":"en","state":"dormant","description":"A builder of things."},{"feedId":"feed/http://prog21.dadgum.com/atom.xml","id":"feed/http://prog21.dadgum.com/atom.xml","title":"Programming in the 21st Century","updated":1483509600000,"velocity":0.0,"subscribers":1,"website":"http://prog21.dadgum.com/","language":"en","state":"dormant"},{"feedId":"feed/http://feeds.feedburner.com/alistapart/main","id":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","updated":1569829877486,"velocity":4.5,"subscribers":1,"website":"https://alistapart.com","language":"en","description":"Articles for people who make web sites."},{"feedId":"feed/http://www.russbishop.net/feed","id":"feed/http://www.russbishop.net/feed","title":"Russ Bishop (atom)","updated":1551122826000,"velocity":0.0,"subscribers":1,"website":"http://www.russbishop.net/feed","language":"en","state":"dormant","description":"This blog represents my own personal opinion and is not endorsed by my employer."},{"feedId":"feed/http://mentalfaculty.tumblr.com/rss","id":"feed/http://mentalfaculty.tumblr.com/rss","title":"The Mental Blog","updated":1468311749000,"velocity":0.0,"subscribers":1,"website":"https://mentalfaculty.tumblr.com/","language":"en","state":"dormant","description":"Drew McCormack (@drewmccormack) is founder of The Mental Faculty, developer of Mental Case and the Ensembles sync framework"},{"feedId":"feed/http://blog.amyworrall.com/rss","id":"feed/http://blog.amyworrall.com/rss","title":"What Amy Did","updated":1486165238000,"velocity":0.0,"subscribers":1,"website":"https://blog.amyworrall.com/","language":"en","state":"dormant","description":"I’m a software developer from Coventry. I care about design and user experience. \n\nFollow me on Twitter: @amyruthworrall"},{"feedId":"feed/http://oleb.net/blog/atom.xml","id":"feed/http://oleb.net/blog/atom.xml","title":"Ole Begemann: iOS Development","updated":1559322747743,"velocity":0.1,"subscribers":1,"website":"https://oleb.net/blog/","language":"en"},{"feedId":"feed/http://subjc.com/atom.xml","id":"feed/http://subjc.com/atom.xml","title":"Subjective-C","updated":1461251700000,"velocity":0.0,"subscribers":2,"website":"http://subjc.com/","language":"en","state":"dormant"},{"feedId":"feed/http://robnapier.net/atom.xml","id":"feed/http://robnapier.net/atom.xml","title":"Cocoaphony","updated":1558929600000,"velocity":0.1,"subscribers":1,"website":"https://robnapier.net/","language":"en"},{"feedId":"feed/http://cocoamanifest.net/feeds/index.xml","id":"feed/http://cocoamanifest.net/feeds/index.xml","title":"Cocoa Manifest","updated":1402642440000,"velocity":0.0,"subscribers":2,"website":"http://cocoamanifest.net","language":"en","state":"dormant"},{"feedId":"feed/http://petersteinberger.com/atom.xml","id":"feed/http://petersteinberger.com/atom.xml","title":"Peter Steinberger","updated":1438088460000,"velocity":0.0,"subscribers":1,"website":"http://petersteinberger.com/","language":"en","state":"dormant"},{"feedId":"feed/http://www.weheartswift.com/feed/","id":"feed/http://www.weheartswift.com/feed/","title":"We ❤ Swift","updated":1569829929574,"velocity":0.2,"subscribers":1,"website":"https://www.weheartswift.com","language":"en","description":"Swift Tutorials and iOS development"},{"feedId":"feed/https://medium.com/feed/swift-programming","id":"feed/https://medium.com/feed/swift-programming","title":"Swift Programming — Medium","updated":1554317410000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/swift-programming?source=rss----5396e0e8bc29---4","language":"en","state":"dormant","description":"The Swift Programming Language - Medium"},{"feedId":"feed/http://tonyarnold.com/atom.xml","id":"feed/http://tonyarnold.com/atom.xml","title":"The blog of Tony Arnold","updated":1531312200000,"velocity":0.0,"subscribers":1,"website":"https://tonyarnold.com","language":"en","state":"dormant"},{"feedId":"feed/http://borkware.com/miniblog/rss/rss.xml","id":"feed/http://borkware.com/miniblog/rss/rss.xml","title":"Borkware Miniblog","updated":1360542004000,"velocity":0.0,"subscribers":1,"website":"https://borkwarellc.wordpress.com","language":"en","state":"dormant","description":"Bork bork bork bork."},{"feedId":"feed/http://nshipster.com/feed.xml","id":"feed/http://nshipster.com/feed.xml","title":"NSHipster","updated":1569418049005,"velocity":0.9,"subscribers":2,"website":"https://nshipster.com/","language":"en","description":"NSHipster is a journal of the overlooked bits in Objective-C, Swift, and Cocoa. Updated weekly."},{"feedId":"feed/http://feeds.feedburner.com/pilkyme","id":"feed/http://feeds.feedburner.com/pilkyme","title":"Pilky.me","updated":1561420800000,"velocity":0.1,"subscribers":1,"website":"https://pilky.me/","language":"en"},{"feedId":"feed/http://macoscope.com/blog/feed/","id":"feed/http://macoscope.com/blog/feed/","title":"[macoscope blog]","updated":1467803080000,"velocity":0.0,"subscribers":1,"website":"http://macoscope.com/blog","language":"en","state":"dormant","description":"The Macoscope Team on Designing and Developing Apps"},{"feedId":"feed/http://iosunittesting.com/feed/","id":"feed/http://iosunittesting.com/feed/","title":"iOS Unit Testing","updated":1436534796000,"velocity":0.0,"subscribers":1,"website":"http://iosunittesting.com","language":"en","state":"dormant","description":"It's about TDD, unit testing, and creating bug free code on iOS."},{"feedId":"feed/http://airspeedvelocity.net/feed/","id":"feed/http://airspeedvelocity.net/feed/","title":"Airspeed Velocity","updated":1452449244000,"velocity":0.0,"subscribers":1,"website":"https://airspeedvelocity.net","language":"en","state":"dormant","description":"African or European Swift?"},{"feedId":"feed/http://www.cimgf.com/feed/","id":"feed/http://www.cimgf.com/feed/","title":"Cocoa Is My Girlfriend","updated":1525988390000,"velocity":0.0,"subscribers":1,"website":"http://www.cimgf.com","language":"en","state":"dormant","description":"Taglines are for Windows programmers"},{"feedId":"feed/http://confusatory.org/rss","id":"feed/http://confusatory.org/rss","title":"The Confusatory","updated":1476982283000,"velocity":0.0,"subscribers":1,"website":"https://confusatory.org/","language":"en","state":"dormant","description":"cbowns’s tumblr."},{"feedId":"feed/http://indiestack.com/feed/","id":"feed/http://indiestack.com/feed/","title":"Indie Stack","updated":1569829987669,"velocity":0.2,"subscribers":1,"website":"https://indiestack.com","language":"en","description":"Hacking the Mac, iOS, and more with Daniel Jalkut"}],"label":"Programming","created":1569829874442,"enterprise":false,"numFeeds":26,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5"},{"customizable":true,"feeds":[{"feedId":"feed/http://feedpress.me/sixcolors","id":"feed/http://feedpress.me/sixcolors","title":"Six Colors","updated":1569955568802,"velocity":24.4,"subscribers":1,"website":"https://www.sixcolors.com/","language":"en","description":"Writing about Apple and other stuff by Jason Snell, Dan Moren, and others."},{"feedId":"feed/http://corinnekrych.blogspot.com/feeds/posts/default","id":"feed/http://corinnekrych.blogspot.com/feeds/posts/default","title":"chat & code","updated":1547807580000,"velocity":0.0,"subscribers":1,"website":"http://corinnekrych.blogspot.com/","language":"en","state":"dormant","description":"Code is craft and collaboration is key to success. I love chatting the latest tech trends at coffee break: female geek."},{"feedId":"feed/http://ericasadun.com/feed/","id":"feed/http://ericasadun.com/feed/","title":"Erica Sadun","updated":1569452593000,"velocity":0.7,"subscribers":1,"website":"https://ericasadun.com","language":"en","description":"Where technology meets something or other"},{"feedId":"feed/http://therecord.co/xml/rss.xml","id":"feed/http://therecord.co/xml/rss.xml","title":"The Record","updated":1401364800000,"velocity":0.0,"subscribers":1,"website":"http://therecord.co/","language":"en","state":"dormant","description":"The stories you should know about the Mac and Cocoa developer community. Hosted by Brent Simmons and Chris Parrish."},{"feedId":"feed/https://grokswift.com/feed/index.xml","id":"feed/https://grokswift.com/feed/index.xml","title":"Grok Swift","updated":1527175834000,"velocity":0.0,"subscribers":1,"website":"https://grokswift.com/","language":"en","state":"dormant"},{"feedId":"feed/https://blog.alltheflow.com/rss/","id":"feed/https://blog.alltheflow.com/rss/","title":"All The Flow","updated":1551711655000,"velocity":0.0,"subscribers":1,"website":"http://blog.alltheflow.com/","language":"en","state":"dormant","description":"Cocoa, Swift, tools, Auto Layout - with 🧡"},{"feedId":"feed/http://onefoottsunami.com/feed/atom/","id":"feed/http://onefoottsunami.com/feed/atom/","title":"One Foot Tsunami","updated":1569938444527,"velocity":5.0,"subscribers":1,"website":"https://onefoottsunami.com","language":"en","description":"Slightly less disappointing than it sounds"},{"feedId":"feed/http://www.loopinsight.com/feed/","id":"feed/http://www.loopinsight.com/feed/","title":"Loop Insight","updated":1569953886300,"velocity":7.9,"subscribers":1,"website":"https://www.loopinsight.com","language":"en","description":"Making Sense of Technology"},{"feedId":"feed/http://beckyhansmeyer.com/feed/","id":"feed/http://beckyhansmeyer.com/feed/","title":"Becky Hansmeyer","updated":1569087367000,"velocity":1.4,"subscribers":1,"website":"https://beckyhansmeyer.com","language":"en","description":"100% grass-fed Swift"},{"feedId":"feed/http://designatednerd.com/feed/","id":"feed/http://designatednerd.com/feed/","title":"Designated Nerd","updated":1564425470000,"velocity":0.1,"subscribers":1,"website":"http://designatednerd.com","language":"en","description":"Software and Technical Support"},{"feedId":"feed/http://appcamp4girls.com/blog?format=RSS","id":"feed/http://appcamp4girls.com/blog?format=RSS","title":"Blog - App Camp For Girls","updated":1558129314000,"velocity":0.1,"subscribers":1,"website":"https://appcamp4girls.com/blog/","language":"en"},{"feedId":"feed/https://inspiredmouse.com/feed/","id":"feed/https://inspiredmouse.com/feed/","title":"Inspired Mouse","updated":1511986957000,"velocity":0.0,"subscribers":1,"website":"https://inspiredmouse.com","language":"en","state":"dormant","description":"No project is too diminutive"},{"feedId":"feed/https://incrementalistblog.wordpress.com/feed/","id":"feed/https://incrementalistblog.wordpress.com/feed/","title":"The Incrementalist.","updated":1449808490000,"velocity":0.0,"subscribers":1,"website":"https://incrementalistblog.wordpress.com","language":"en","state":"dormant","description":"notes on design"},{"feedId":"feed/http://www.virginiaroberts.com/feed/","id":"feed/http://www.virginiaroberts.com/feed/","title":"Virginia Roberts","updated":1549571065000,"velocity":0.0,"subscribers":1,"website":"http://www.virginiaroberts.com","language":"en","state":"dormant"},{"feedId":"feed/http://jessysaurusrex.com/feed/","id":"feed/http://jessysaurusrex.com/feed/","title":"jessysaurusrex","updated":1521816327000,"velocity":0.0,"subscribers":1,"website":"https://jessysaurusrex.com","language":"en","state":"dormant","description":"I wear big necklaces and (attempt to) hack things."},{"feedId":"feed/http://blog.nicoleblee.com/feed/","id":"feed/http://blog.nicoleblee.com/feed/","title":"scattered thoughts","updated":1397441482000,"velocity":0.0,"subscribers":1,"website":"http://blog.nicoleblee.com","language":"en","state":"dormant","description":"(a collection of essays and other writings)"},{"feedId":"feed/https://medium.com/feed/@tessr","id":"feed/https://medium.com/feed/@tessr","title":"Tess Rinearson on Medium","updated":1501016354000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@tessr?source=rss-c16152863954------2","language":"en","state":"dormant","description":"Stories by Tess Rinearson on Medium"},{"feedId":"feed/https://medium.com/feed/@emarley","id":"feed/https://medium.com/feed/@emarley","title":"Liz Marley on Medium","updated":1514047622000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@emarley?source=rss-b4981c59ffa5------2","language":"en","state":"dormant","description":"Stories by Liz Marley on Medium"},{"feedId":"feed/http://blog.cocoabythefire.com/rss","id":"feed/http://blog.cocoabythefire.com/rss","title":"cocoa by the fire","updated":1488945726000,"velocity":0.0,"subscribers":1,"website":"https://blog.cocoabythefire.com/","language":"en","state":"dormant","description":"Hey there, I’m Brit! Coder, Entrepreneur, Daydreamer, Lucky Wife and Mom."},{"feedId":"feed/http://www.catehuston.com/blog/feed/","id":"feed/http://www.catehuston.com/blog/feed/","title":"Accidentally in Code","updated":1567641655000,"velocity":0.2,"subscribers":1,"website":"https://cate.blog","language":"en","description":"Engineering an Interesting Life"},{"feedId":"feed/http://www.aleenmean.com/feed.xml","id":"feed/http://www.aleenmean.com/feed.xml","title":"Aleen Mean","updated":1562374146000,"velocity":0.1,"subscribers":1,"website":"https://aleenmean.com/","language":"en","description":"Technology, diversity, and miscellaneous musings by Aleen Simms."},{"feedId":"feed/http://redqueencoder.com/feed/","id":"feed/http://redqueencoder.com/feed/","title":"The Red Queen Coder","updated":1569967579535,"velocity":0.2,"subscribers":1,"website":"http://redqueencoder.com","language":"en","description":"If you give a person a program, you'll frustrate him for a day. If you teach a person to program, you will frustrate them for a lifetime!"},{"feedId":"feed/http://meaganwaller.com/index.php/feed/","id":"feed/http://meaganwaller.com/index.php/feed/","title":"Meagan Waller","updated":1403205537000,"velocity":0.0,"subscribers":1,"website":"https://meaganwaller.com","language":"en","state":"dormant"},{"feedId":"feed/http://www.myballard.com/feed/","id":"feed/http://www.myballard.com/feed/","title":"Ballard","updated":1569963964108,"velocity":8.1,"subscribers":1,"website":"https://www.myballard.com","language":"en","description":"News, events and restaurants for Seattle's Ballard and Fremont neighborhoods"},{"feedId":"feed/https://kateheddleston.com/blog/feed.atom","id":"feed/https://kateheddleston.com/blog/feed.atom","title":"KateHeddleston.com Blog Posts","updated":1526573156000,"velocity":0.0,"subscribers":1,"website":"https://www.kateheddleston.com/blog","language":"en","state":"dormant"},{"feedId":"feed/http://blog.erynwells.me/rss","id":"feed/http://blog.erynwells.me/rss","title":"Eryn Wells","updated":1442175535000,"velocity":0.0,"subscribers":1,"website":"https://blog.erynwells.me/","language":"en","state":"dormant"},{"feedId":"feed/http://lambdamaphone.blogspot.com/feeds/posts/default","id":"feed/http://lambdamaphone.blogspot.com/feeds/posts/default","title":"Everything in Context","updated":1519599000002,"velocity":0.0,"subscribers":1,"website":"http://lambdamaphone.blogspot.com/","language":"en","state":"dormant","description":"Game design, programming languages, and academia."},{"feedId":"feed/http://nothe.purplellamas.net/index.xml","id":"feed/http://nothe.purplellamas.net/index.xml","title":"Blog Posts About Stuff","updated":1512518400000,"velocity":0.0,"subscribers":1,"website":"http://nothe.purplellamas.net/","language":"en","state":"dormant","description":"Recent content on Blog Posts About Stuff"},{"feedId":"feed/http://www.mostgood.net/blog?format=RSS","id":"feed/http://www.mostgood.net/blog?format=RSS","title":"mostgood","updated":1433114183000,"velocity":0.0,"subscribers":1,"website":"http://www.mostgood.net/","language":"en","state":"dormant"},{"feedId":"feed/http://www.mistys-internet.website/blog/atom.xml","id":"feed/http://www.mistys-internet.website/blog/atom.xml","title":"The Future Is Now","updated":1550722972000,"velocity":0.0,"subscribers":1,"website":"http://mistys-internet.website/blog/","language":"en","state":"dormant"},{"feedId":"feed/http://pewpewthespells.com/feed.xml","id":"feed/http://pewpewthespells.com/feed.xml","title":"Samantha Marshall's Blog","updated":1569829870614,"velocity":8.6,"subscribers":1,"website":"https://pewpewthespells.com/","language":"en","description":"Blog Feed"},{"feedId":"feed/http://blog.ashleynh.me/rss/","id":"feed/http://blog.ashleynh.me/rss/","title":"Ashley Nelson-Hornstein","updated":1496606482000,"velocity":0.0,"subscribers":1,"website":"http://ashleynh.me:80/","language":"en","state":"dormant","description":"Ashley Nelson"},{"feedId":"feed/https://medium.com/feed/@nerdonica","id":"feed/https://medium.com/feed/@nerdonica","title":"Veronica Ray on Medium","updated":1471184706000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@nerdonica?source=rss-eaf18ccd367f------2","language":"en","state":"dormant","description":"Stories by Veronica Ray on Medium"},{"feedId":"feed/http://www.nadynerichmond.com/blog/feed/","id":"feed/http://www.nadynerichmond.com/blog/feed/","title":"go ahead, mac my day","updated":1506105463000,"velocity":0.0,"subscribers":1,"website":"http://www.nadynerichmond.com/blog","language":"en","state":"dormant","description":"a Macintosh girl in a Microsoft world"},{"feedId":"feed/http://www.bbc.co.uk/blogs/doctorwho/rss","id":"feed/http://www.bbc.co.uk/blogs/doctorwho/rss","title":"Doctor Who","updated":1567440000000,"velocity":0.2,"subscribers":1,"website":"https://www.bbc.co.uk/blogs/doctorwho","language":"en","description":"All the latest news and features from the world of Doctor Who."},{"feedId":"feed/http://scripting.com/rss.xml","id":"feed/http://scripting.com/rss.xml","title":"Scripting News","updated":1569956453445,"velocity":15.4,"subscribers":1,"website":"http://scripting.com/","language":"en","description":"Scripting News, the weblog started in 1994 that bootstrapped the blogging revolution. 🚀"},{"feedId":"feed/http://daringfireball.net/feeds/main","id":"feed/http://daringfireball.net/feeds/main","title":"Daring Fireball","updated":1569960343721,"velocity":13.1,"subscribers":1,"website":"https://daringfireball.net/","language":"en","description":"By John Gruber"},{"feedId":"feed/http://www.mechanicalgirl.com/feeds/all/","id":"feed/http://www.mechanicalgirl.com/feeds/all/","title":"MechanicalGirl","updated":1569829801167,"velocity":1.1,"subscribers":1,"website":"http://www.MechanicalGirl.com/","language":"en","description":"Latest posts on MechanicalGirl"},{"feedId":"feed/http://ranchero.com/xml/rss.xml","id":"feed/http://ranchero.com/xml/rss.xml","title":"ranchero.com","updated":1569971250209,"velocity":2.3,"subscribers":1,"website":"https://inessential.com/","language":"en","description":"Brent Simmons’s weblog."},{"feedId":"feed/http://natashatherobot.com/feed/","id":"feed/http://natashatherobot.com/feed/","title":"Natasha The Robot","updated":1569829929101,"velocity":0.2,"subscribers":2,"website":"https://www.natashatherobot.com","language":"en"},{"feedId":"feed/http://daringfireball.net/index.xml","id":"feed/http://daringfireball.net/index.xml","title":"Daring Fireball","updated":1569959070397,"velocity":15.6,"subscribers":6,"website":"https://daringfireball.net/","language":"en","description":"By John Gruber"},{"feedId":"feed/http://timekl.com/atom.xml","id":"feed/http://timekl.com/atom.xml","title":"don't panic","updated":1555225200000,"velocity":0.0,"subscribers":1,"website":"https://timekl.com/","language":"en","description":"Occasional posts, usually about technology"},{"feedId":"feed/http://nataliepo.typepad.com/nataliepo/rss.xml","id":"feed/http://nataliepo.typepad.com/nataliepo/rss.xml","title":"nataliepo (posts on 'nataliepo' (rss 2.0))","updated":1447256454000,"velocity":0.0,"subscribers":1,"website":"https://nataliepo.typepad.com/nataliepo/","language":"en","state":"dormant"},{"feedId":"feed/http://shapeof.com/rss.xml","id":"feed/http://shapeof.com/rss.xml","title":"The Shape of Everything","updated":1569263479000,"velocity":1.6,"subscribers":1,"website":"https://shapeof.com/","language":"en","description":"A website mostly about Mac stuff, written by Gus Mueller"},{"feedId":"feed/http://jvns.ca/atom.xml","id":"feed/http://jvns.ca/atom.xml","title":"Julia Evans","updated":1569959500644,"velocity":4.3,"subscribers":2,"website":"http://jvns.ca","language":"en"},{"feedId":"feed/https://www.natashatherobot.com/feed/","id":"feed/https://www.natashatherobot.com/feed/","title":"Natasha the Robot","updated":1545498499000,"velocity":0.0,"subscribers":1,"website":"https://www.natashatherobot.com","language":"en","state":"dormant"},{"feedId":"feed/http://pointersgonewild.com/feed/","id":"feed/http://pointersgonewild.com/feed/","title":"Pointers Gone Wild","updated":1560168135000,"velocity":0.1,"subscribers":1,"website":"https://pointersgonewild.com","language":"en","description":"A blog about compilers, programming and technology."},{"feedId":"feed/http://www.kristinathai.com/feed/","id":"feed/http://www.kristinathai.com/feed/","title":"kristinathai.com","updated":1563831802000,"velocity":0.1,"subscribers":1,"website":"http://www.kristinathai.com","language":"ja"},{"feedId":"feed/https://developer.apple.com/swift/blog/news.rss","id":"feed/https://developer.apple.com/swift/blog/news.rss","title":"Swift Blog - Apple Developer","updated":1476306000000,"velocity":0.0,"subscribers":1,"website":"https://developer.apple.com/swift/blog/","language":"en","state":"dormant","description":"Get the latest news and helpful tips on the Swift programming language from the engineers who created it."},{"feedId":"feed/http://www.rebeccamiller-webster.com/feed/","id":"feed/http://www.rebeccamiller-webster.com/feed/","title":"Rebecca Miller-Webster","updated":1547836736000,"velocity":0.0,"subscribers":1,"website":"https://www.rebeccamiller-webster.com","language":"en","state":"dormant","description":"Ruby + JavaScript"},{"feedId":"feed/http://swift.ayaka.me/posts?format=RSS","id":"feed/http://swift.ayaka.me/posts?format=RSS","title":"Learn Swift ↯","updated":1466314290000,"velocity":0.0,"subscribers":1,"website":"http://swift.ayaka.me/","language":"en","state":"dormant"},{"feedId":"feed/http://www.imore.com/rss.xml","id":"feed/http://www.imore.com/rss.xml","title":"iMore","updated":1569969502355,"velocity":17.8,"subscribers":1,"website":"https://www.imore.com/","language":"en","description":"More news and rumors, more help and how-tos, more app and accessory reviews, more iPhone and iPad and iPod touch. More of everything you love. iMore."},{"feedId":"feed/http://blog.thoughtbrain.com/feed/","id":"feed/http://blog.thoughtbrain.com/feed/","title":"Feed: Thoughtbrain Bloggers","updated":1426140251000,"velocity":0.0,"subscribers":1,"website":"http://blog.thoughtbrain.com","language":"en","state":"dormant","description":"Designery nerdy things."},{"feedId":"feed/http://blog.ellenchisa.com/feed/","id":"feed/http://blog.ellenchisa.com/feed/","title":"Ellen's Blog","updated":1546640740000,"velocity":0.0,"subscribers":1,"website":"https://blog.ellenchisa.com?source=rss----da542b929da2---4","language":"en","state":"dormant","description":"I’m starting a new company with @paulbiggar, which you can learn about at https://darklang.com. I mostly write about startups and software development. - Medium"},{"feedId":"feed/https://medium.com/feed/@jaimeejaimee","id":"feed/https://medium.com/feed/@jaimeejaimee","title":"jaimeejaimee","updated":1558376026000,"velocity":0.1,"subscribers":1,"website":"https://medium.com/@jaimeejaimee?source=rss-11d5cc4494a2------2","language":"en","description":"Stories by jaimeejaimee on Medium"}],"label":"Uncategorized","created":1569829699432,"enterprise":false,"numFeeds":55,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8"},{"customizable":true,"feeds":[{"feedId":"feed/http://bryan.io/rss","id":"feed/http://bryan.io/rss","title":"bryan i/o","updated":1567230243000,"velocity":0.2,"subscribers":1,"website":"https://bryan.io/","language":"en","description":"Software engineer who led iOS at Tumblr from 2012-2015. Mostly cheeseburgers at this point. Over at irace.me nowadays."},{"feedId":"feed/http://nickbradbury.com/feed/","id":"feed/http://nickbradbury.com/feed/","title":"Nick Bradbury","updated":1503946502000,"velocity":0.0,"subscribers":1,"website":"https://nickbradbury.com","language":"en","state":"dormant","description":"I develop Android apps. In a previous life I created HomeSite, TopStyle and FeedDemon for Windows."},{"feedId":"feed/http://feeds.feedburner.com/domainofthebored","id":"feed/http://feeds.feedburner.com/domainofthebored","title":"Peter Hosey","updated":1567916255000,"velocity":0.2,"subscribers":1,"website":"https://boredzo.org/blog","language":"en","description":"The personal weblog of Peter Hosey."},{"feedId":"feed/http://blog.metaobject.com/feeds/posts/default","id":"feed/http://blog.metaobject.com/feeds/posts/default","title":"metablog","updated":1556396880001,"velocity":0.0,"subscribers":1,"website":"https://blog.metaobject.com/","language":"en"},{"feedId":"feed/http://feeds2.feedburner.com/adobe/jnack","id":"feed/http://feeds2.feedburner.com/adobe/jnack","title":"John Nack on Adobe (rss (feedburner))","updated":1391820146000,"velocity":0.0,"subscribers":1,"website":"http://blogs.adobe.com/jnack","language":"en","state":"dormant"},{"feedId":"feed/http://www.zathras.de/angelweb/BlogRSSFeed.rss","id":"feed/http://www.zathras.de/angelweb/BlogRSSFeed.rss","title":"Zathras.de - Uli's most useless blog in the World","updated":1562364000000,"velocity":0.1,"subscribers":1,"website":"https://orangejuiceliberationfront.com/","language":"en","description":"Uli's blog on programming, game development, pop culture and other boring things."},{"feedId":"feed/http://typesetinthefuture.com/feed/","id":"feed/http://typesetinthefuture.com/feed/","title":"Typeset In The Future","updated":1544536375000,"velocity":0.0,"subscribers":1,"website":"https://typesetinthefuture.com","language":"en","state":"dormant","description":"Typography and Design in Science Fiction Movies"},{"feedId":"feed/http://david-smith.org/atom.xml","id":"feed/http://david-smith.org/atom.xml","title":"David Smith","updated":1569830048565,"velocity":0.5,"subscribers":1,"website":"http://david-smith.org/","language":"en"},{"feedId":"feed/http://awkwardhare.com/rss","id":"feed/http://awkwardhare.com/rss","title":"Awkward Hare","updated":1484149569000,"velocity":0.0,"subscribers":1,"website":"https://awkwardhare.com/","language":"en","state":"dormant","description":"A blog by Greg Pierce"},{"feedId":"feed/http://frozendevil.com/atom.xml","id":"feed/http://frozendevil.com/atom.xml","title":"frozendevil","updated":1398927600000,"velocity":0.0,"subscribers":1,"website":"http://frozendevil.com/","language":"en","state":"dormant"},{"feedId":"feed/http://useyourloaf.com/blog/rss.xml","id":"feed/http://useyourloaf.com/blog/rss.xml","title":"Use Your Loaf","updated":1569830765949,"velocity":0.5,"subscribers":2,"website":"https://useyourloaf.com/blog/","language":"en","description":"Recent content on Use Your Loaf - iOS Development News & Tips"},{"feedId":"feed/http://www.appleoutsider.com/feed/","id":"feed/http://www.appleoutsider.com/feed/","title":"Apple Outsider","updated":1402413759000,"velocity":0.0,"subscribers":1,"website":"https://www.appleoutsider.com","language":"en","state":"dormant"},{"feedId":"feed/http://rathole.tumblr.com/rss","id":"feed/http://rathole.tumblr.com/rss","title":"RatHole","updated":1554476924000,"velocity":0.0,"subscribers":1,"website":"https://rathole.tumblr.com/","language":"en","description":"what my brain does when I’m not looking"},{"feedId":"feed/http://codeplease.io/rss/","id":"feed/http://codeplease.io/rss/","title":"Codeplease","updated":1511101649000,"velocity":0.2,"subscribers":1,"website":"http://codeplease.io/","language":"en","state":"dead","description":"Ramblings about code"},{"feedId":"feed/http://blog.jaredsinclair.com/rss?1","id":"feed/http://blog.jaredsinclair.com/rss?1","title":"Jared Sinclair","updated":1554679244000,"velocity":0.0,"subscribers":1,"website":"https://jaredsinclair.com/","language":"en","description":"Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description."},{"feedId":"feed/http://www.takingnotes.co/atom.xml","id":"feed/http://www.takingnotes.co/atom.xml","title":"Doug Russell","updated":1541721600000,"velocity":0.0,"subscribers":1,"website":"http://takingnotes.co//","language":"en","state":"dormant"},{"feedId":"feed/http://www.red-sweater.com/blog/feed","id":"feed/http://www.red-sweater.com/blog/feed","title":"Red Sweater","updated":1568998271000,"velocity":0.5,"subscribers":1,"website":"https://red-sweater.com/blog","language":"en","description":"Official blog of Red Sweater Software"},{"feedId":"feed/http://mjtsai.com/blog/feed/","id":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","updated":1569964972836,"velocity":18.7,"subscribers":1,"website":"https://mjtsai.com/blog","language":"en"},{"feedId":"feed/http://jnack.com/blog/?feed=rss2","id":"feed/http://jnack.com/blog/?feed=rss2","title":"Nackblog","updated":1569939043384,"velocity":2.7,"subscribers":1,"website":"http://jnack.com/blog","language":"en","description":"Musings on photography, illustration, mobile apps, and more"},{"feedId":"feed/http://bitsplitting.org/feed/","id":"feed/http://bitsplitting.org/feed/","title":"Daniel Jalkut","updated":1563906209098,"velocity":0.1,"subscribers":1,"website":"https://bitsplitting.org","language":"en","description":"Chasing the impossible with Daniel Jalkut"},{"feedId":"feed/http://stmts.net/feed/","id":"feed/http://stmts.net/feed/","title":"Jesper","updated":1298233430000,"velocity":0.0,"subscribers":1,"website":"http://stmts.net","language":"en","state":"dormant","description":"On programming"},{"feedId":"feed/http://www.mikeash.com/pyblog/rss.py","id":"feed/http://www.mikeash.com/pyblog/rss.py","title":"NSBlog","updated":1530280470876,"velocity":0.0,"subscribers":2,"website":"http://www.mikeash.com/pyblog/","language":"en","state":"dormant","description":"Mac OS X and Cocoa programming"},{"feedId":"feed/http://corporationunknown.com/blog/feed/","id":"feed/http://corporationunknown.com/blog/feed/","title":"Corporation Unknown","updated":1420427842000,"velocity":0.0,"subscribers":1,"website":"http://corporationunknown.com/blog","language":"en","state":"dormant"},{"feedId":"feed/http://jamesdempsey.net/feed/","id":"feed/http://jamesdempsey.net/feed/","title":"James Dempsey","updated":1568304414000,"velocity":0.2,"subscribers":1,"website":"https://jamesdempsey.net","language":"en","description":"From Apple to Indie in three easy steps"},{"feedId":"feed/http://brian-webster.tumblr.com/rss","id":"feed/http://brian-webster.tumblr.com/rss","title":"Very Web. Such Blog. Wow.","updated":1530051926000,"velocity":0.0,"subscribers":1,"website":"https://brian-webster.tumblr.com/","language":"en","state":"dormant","description":"Brian Webster’s sporadic blogging about mostly programming stuff."},{"feedId":"feed/http://dangillmor.com/feed/","id":"feed/http://dangillmor.com/feed/","title":"Dan Gillmor","updated":1565895640000,"velocity":0.1,"subscribers":1,"website":"http://dangillmor.com","language":"en","description":"Just in case you were still wondering…"},{"feedId":"feed/http://www.jeffmcleman.com/blog/feed/","id":"feed/http://www.jeffmcleman.com/blog/feed/","title":"Jeff McLeman","updated":1563565996000,"velocity":0.1,"subscribers":1,"website":"https://www.jeffmcleman.com/blog","language":"en","description":"The Brooding Thoughts of an Untamed Mind"},{"feedId":"feed/http://www.caseyliss.com/rss","id":"feed/http://www.caseyliss.com/rss","title":"Liss is More","updated":1569830046093,"velocity":0.5,"subscribers":1,"website":"https://www.caseyliss.com","language":"en","description":"Posts to Liss is More"},{"feedId":"feed/http://ignorethecode.net/blog/rss/","id":"feed/http://ignorethecode.net/blog/rss/","title":"ignorethecode.net","updated":1532170394000,"velocity":0.0,"subscribers":1,"website":"http://ignorethecode.net","language":"en","state":"dormant","description":"Essays on usability, programming, and other nerd topics."},{"feedId":"feed/http://sheilasweblog.wordpress.com/feed/","id":"feed/http://sheilasweblog.wordpress.com/feed/","title":"Sheila's Weblog","updated":1237602766000,"velocity":0.0,"subscribers":1,"website":"https://sheilasweblog.wordpress.com","language":"en","state":"dormant","description":"Quilting, kitties, other fun stuff."},{"feedId":"feed/http://www.allenpike.com/feed/","id":"feed/http://www.allenpike.com/feed/","title":"Allen Pike","updated":1569920023707,"velocity":0.5,"subscribers":1,"website":"https://www.allenpike.com/","language":"en"},{"feedId":"feed/https://developer.apple.com/news/rss/news.rss","id":"feed/https://developer.apple.com/news/rss/news.rss","title":"iPhone Developer News","updated":1569869696571,"velocity":3.2,"subscribers":1,"website":"https://developer.apple.com/news/","language":"en","description":"Apple Developer News and Updates feed provided by Apple, Inc."},{"feedId":"feed/http://themainthread.com/feed.xml","id":"feed/http://themainthread.com/feed.xml","title":"The Main Thread","updated":1440820800000,"velocity":0.0,"subscribers":1,"website":"http://themainthread.com/","language":"en","state":"dormant"},{"feedId":"feed/http://www.gordonmeyer.com/atom.xml","id":"feed/http://www.gordonmeyer.com/atom.xml","title":"Gordon Meyer (posts on 'gordon meyer' (atom))","updated":1569066494000,"velocity":0.5,"subscribers":1,"website":"https://www.gordonmeyer.com/","language":"en"},{"feedId":"feed/http://www.mondaynote.com/feed/","id":"feed/http://www.mondaynote.com/feed/","title":"Monday Note","updated":1569786821000,"velocity":2.0,"subscribers":1,"website":"https://mondaynote.com?source=rss----c537d80ed0a---4","language":"en","description":"Media, Tech, Business Models viewed from Palo Alto and Paris - Medium"},{"feedId":"feed/http://www.randsinrepose.com/index.xml","id":"feed/http://www.randsinrepose.com/index.xml","title":"Rands In Repose","updated":1569830107339,"velocity":0.2,"subscribers":4,"website":"https://randsinrepose.com","language":"en"},{"feedId":"feed/http://furbo.org/feed/","id":"feed/http://furbo.org/feed/","title":"furbo.org","updated":1569830050733,"velocity":0.5,"subscribers":1,"website":"https://furbo.org","language":"en","description":"by Craig Hockenberry"},{"feedId":"feed/http://feeds.feedburner.com/NSHipster","id":"feed/http://feeds.feedburner.com/NSHipster","title":"NSHipster","updated":1569222000000,"velocity":0.7,"subscribers":1,"website":"https://nshipster.com/","language":"en","description":"NSHipster is a journal of the overlooked bits in Objective-C, Swift, and Cocoa. Updated weekly."},{"feedId":"feed/http://www.neglectedpotential.com/feed/","id":"feed/http://www.neglectedpotential.com/feed/","title":"Neglected Potential","updated":1537811051000,"velocity":0.0,"subscribers":1,"website":"http://www.neglectedpotential.com","language":"en","state":"dormant"}],"label":"Weblogs","created":1569829952574,"enterprise":false,"numFeeds":39,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366"}] \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/AddFeed/mustread_addfeed.json b/Frameworks/Account/AccountTests/Feedly/AddFeed/mustread_addfeed.json new file mode 100644 index 000000000..b8a5afd50 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/AddFeed/mustread_addfeed.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","updated":1569971230061,"continuation":"16d81261cbd:4d986:18991ffa","items":[{"originId":"https://inessential.com/2019/10/01/on_bullying_in_our_community","fingerprint":"446362f9","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d8993d56d:5148e:18991ffa","summary":{"direction":"ltr","content":"

Janie Larson writes about being bullied, and you should read it.

\n

It’s natural to wonder who the bully is and who the conference organizers are — but I’m resisting the temptation to spend any time on it. It’s not a puzzle to be solved. Janie’s explicit that she doesn’t want this to result in anyone getting harassed, and she doesn’t want to start a feud. Respect that.

\n

Instead, she talks about the human cost of being bullied, and she presents a guide for handling bullying — which is written especially for people witnessing it.

\n

Even if you think it’s unlikely that you yourself will ever be bullied (and you might not think that), it’s worth remembering that you might see it happen to someone else. I hope you and I would do the right thing.

"},"alternate":[{"href":"https://inessential.com/2019/10/01/on_bullying_in_our_community","type":"text/html"}],"crawled":1569971230061,"title":"On Bullying in Our Community","published":1569969481000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"visual":{"url":"http://cdn3.sbnation.com/entry_photo_images/9192995/Untitled_large.jpg","width":630,"height":420,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/10/01/nanowrimo_is_next_month_and_i_will_conti","fingerprint":"8549ffd6","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d8924f769:5121a:18991ffa","summary":{"direction":"ltr","content":"

NaNoWriMo is next month, and I will continue my streak of not participating in it. I’m super-impressed by the people who do, though.

\n

It would take me a month of hard, solid work all November to decide on an idea to write about, then another month to think it through some more — or two months, really, because the holidays get in the way — and then about a year of nightly work to decide on a plot outline and characters and tone, and then another year of refining that outline, and then, by NaNoWriMo 2021 or 2022, I’d be ready to start writing. I suspect I’d average about 300 words a day, which would get me about 9,000 words for the month — which is well less than a novel or even the 50,000 words goal.

\n

I blog instead.

\n

PS What made me think of this: Cheri Baker, Let’s Half-Ass NaNoWriMo Together.

"},"alternate":[{"href":"https://inessential.com/2019/10/01/nanowrimo_is_next_month_and_i_will_conti","type":"text/html"}],"crawled":1569963964265,"published":1569960276000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/galaxygear-lead.jpg","width":619,"height":411,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/10/01/this_leaked_audio_from_facebook_https_ww","fingerprint":"abd6439f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d884666a1:50bba:18991ffa","summary":{"direction":"ltr","content":"

This leaked audio from Facebook — where Mark Zuckerberg promises that “you go to the mat and you fight” Elizabeth Warren — is a reminder: any corporation that has the power over the speech of billions of people is still a corporation with its own interests. And those interests don’t match yours or mine or the interests of democracy.

\n

You don’t have to support, or even like, Elizabeth Warren to understand that.

\n

Do you trust Facebook not to tip the scales in favor of Zuckerberg’s interests? I sure don’t.

\n

This is about Facebook and a specific presidential candidate — and it’s also about giant corporate communications platforms and how they subvert civilization.

\n

PS Reminder: Instagram is Facebook too.

"},"alternate":[{"href":"https://inessential.com/2019/10/01/this_leaked_audio_from_facebook_https_ww","type":"text/html"}],"crawled":1569949378209,"published":1569946773000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","fingerprint":"efab5851","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d996:18991ffa","summary":{"direction":"ltr","content":"

I had the fun of interviewing old friend Daniel Jalkut on the latest episode of The Omni Show.

"},"alternate":[{"href":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","type":"text/html"}],"crawled":1569829821629,"published":1569437386000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","fingerprint":"f53acc86","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d995:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0.1 is almost entirely a bug-fix release — see the release notes for the full scoop.

\n

It includes one sort-of new feature: there’s now a checkbox in Preferences for turning off the unread count in the Dock. (It was a hidden pref — now it’s visible.)

\n

Status

\n

Here’s what else we’re working on:

\n
    \n
  • iOS/iPadOS app
  • \n
  • NetNewsWire 5.0.2 for Mac — which will mainly be about performance (yes, we can make it even faster)
  • \n
  • NetNewsWire 5.1 for Mac — tentative feature list includes content extraction and at least one more syncing option (but we might change our minds on these: anything can happen between now and then)
  • \n
\n

We might also distribute NetNewsWire 5.0.2 for Mac on the Mac App Store. No guarantees yet, of course, but work is happening in that direction. This goes to our goal of getting as many people as possible using RSS readers.

"},"alternate":[{"href":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0.1 Released","published":1568408217000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","fingerprint":"5b6c292f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d994:18991ffa","summary":{"direction":"ltr","content":"

Had to get a new key fob at work today — my old one wore out. Just a couple weeks shy of my fifth anniversary at Omni! Time flies.

\n

I figure I’m just over eight years from retiring, so I’m not even halfway done here. :)

"},"alternate":[{"href":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","type":"text/html"}],"crawled":1569829821629,"published":1568153137000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","fingerprint":"3b5ade1b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d993:18991ffa","summary":{"direction":"ltr","content":"

People have been asking me about supporting iCloud as a sync method for NetNewsWire.

\n

It would be really cool because:

\n
    \n
  • There’s no sign-in
  • \n
  • It’s free — no need to spend money on another service
  • \n
  • It would help broaden the pool of people using RSS, since there would be no additional expense or service they’d need — they could just get going
  • \n
\n

It’s a great idea — no question. Given that my goal is to get as many people as possible using RSS, this makes total sense.

\n

Why we didn’t ship with this feature

\n

For the first release — I still think of it as a 1.0, because it really is — our best bet was to appeal to people already using an existing RSS service. We know that those people like and use RSS, and they’re the people most likely to check out a new RSS app.

\n

(We could have delayed and shipped with support for more existing services, but we figured one was enough to get started with, and we could add other services later. And we are.)

\n

In other words, we tried to make an app that the existing market would like. And that’s the right call when you’re starting out.

\n

Also: iCloud sync makes the most sense when you have both a Mac and an iOS app, and we don’t — the iOS app is still in progress. We totally expect people to use NetNewsWire on the Mac and Unread or Reeder on their iPhone and iPad — and iCloud sync won’t work across apps. This scenario requires using services such as Feedbin.

\n

Why I have no idea when this feature might appear

\n

For any existing RSS service, we can be confident that our effort to support it in NetNewsWire would be successful. This is well-trodden ground: we make some web API calls, integrate with our database, and done. It’s not nothing, but conceptually it’s simple and there’s no cause to worry about technical issues.

\n

But iCloud syncing will mean writing exploratory code and only then finding out if it’s going to work.

\n

Syncing the feeds list should be relatively easy — the real issue is with syncing read/unread/starred states of articles. That means a lot of small records.

\n

Is CloudKit up to this? What are the limits? How fast is it? How reliable?

\n

We just don’t know.

\n

Yes, it’s encouraging that News Explorer has this feature — but that doesn’t tell us much about the limits, reliability, and performance.

\n

Working on this is a risk.

\n

So — as you can imagine — we’re still more keen on supporting existing RSS services, because we know there are plenty of people who for-sure like RSS, and who might like NetNewsWire, but who won’t switch their syncing system just to use NetNewsWire.

\n

That said: I do think we’ll get around to trying this, and I’ll be super-pleased if it works, because it really is a great idea — but we have a bunch of other work to do first. (Including the iOS app!)

"},"alternate":[{"href":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","type":"text/html"}],"crawled":1569829821629,"title":"On Syncing NetNewsWire Using iCloud","published":1567817061000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","fingerprint":"ca200f5a","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d992:18991ffa","summary":{"direction":"ltr","content":"

Markos Charatzas writes about his excitement in joining the Apple developer world in 2009 to his eventual disillusionment today.

"},"alternate":[{"href":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","type":"text/html"}],"crawled":1569829821629,"published":1567788970000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","fingerprint":"66196df9","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d991:18991ffa","summary":{"direction":"ltr","content":"

A number of people have asked that NetNewsWire show the full web page — right there, in the app — after clicking a link.

\n

The idea is pretty good! It solves two big problems:

\n
    \n
  • You get full content, which is great when a feed contains only summaries or truncated articles
  • \n
  • You don’t have to switch to another app: you can stay right where you are
  • \n
\n

You’d think it’s a no-brainer, and we should just go ahead. But there are other considerations.

\n

One big one is that your ad blockers and privacy extensions won’t run. They work in Safari, but they do not extend to other apps that use WebKit. This means that viewing a web page in NetNewsWire would be less secure and more annoying than viewing the same page in Safari (or whatever your browser is).

\n

This points to one of my design principles: the app should have boundaries. Some features belong in the app, and some features are best left to apps that do that feature way better than NetNewsWire could. One of those things is showing web pages — that’s really a web browser feature.

\n

Having boundaries means we can concentrate on doing a great job at the things that do belong in the app.

\n

(Before you mention SFSafariViewController, recall that it’s iOS-only.)

\n

What about the glory days?

\n

“But Brent! In NetNewsWire 2.0 you added a tabbed browser to NetNewsWire, and it was awesome and a hugely popular feature!”

\n

It was! But times have changed. Many websites are hostile these days. In 2005, this feature was fine — but these days it’s totally not.

\n

A winged messenger arrives with a solution

\n

There is a solution to the problem of showing full content and not leaving the app, and it’s a feature that really does belong in an RSS reader: using content extraction to grab the article from the original page.

\n

If you’ve ever used Safari’s Reader view, then you know what I’m talking about. The idea is that NetNewsWire would do something very much like the Reader view (but inline, in the article pane), that grabs the content and formats it nicely, without all the extra junk that is not the article you want to read.

\n

There are a number of open source options for this. We’re looking at using Feedbin’s content extraction service (which wouldn’t require you to have a Feedbin account).

\n

The generous folks at Feedbin are running a copy of the open-source Mercury Parser, and they’ve offered to open this service up to RSS readers like NetNewsWire. (Reeder uses it already, for instance.)

\n

When?

\n

Right now we’re working on NetNewsWire 5.0.1, which is (almost entirely) a bug-fix release. I don’t know what’s going to be in 5.1 yet — we’re still digesting all the feedback, looking at our original roadmap, and thinking about things.

\n

We’re also working on NetNewsWire for iOS! We’re busy.

\n

But this is definitely the kind of feature that should come sooner rather than later.

"},"alternate":[{"href":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"On the Many NetNewsWire Feature Requests to Show Full Web Pages","published":1567661107000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","fingerprint":"bb260103","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d990:18991ffa","summary":{"direction":"ltr","content":"

John Gruber has mentioned, on The Talk Show, that I’ve got some weird ideas about what beta means.

\n

Here are my definitions:

\n

development (d): everything is in progress and the app might be completely unusable.

\n

alpha (a): the app is feature-complete and has no known bugs — but, importantly, it’s had very little testing.

\n

beta (b): the app is feature-complete, has no known bugs, and has been tested — but further testing is still warranted. Every beta is a release candidate.

\n

These are defined in a NetNewsWire Technote. It’s important to have definitions that everybody working on or testing the app understands.

\n

But why these rather strict definitions?

\n

It’s part of our commitment to quality. What matters is the end result — the shipping app — and these definitions make sure we don’t get to beta, or even alpha, with the app up on the table with wires sticking out and pieces missing.

\n

This gives us a big space between development and shipping, and that space is all about making sure the bugs are all fixed.

\n

This is a matter of ethics and pride in our work. Absolutely.

\n

But it’s also pragmatic. This is an open source app, written by volunteers in their spare time, and having this rhythm baked-in to the process helps make sure we can uphold our standards even without full-time developers, managers, and testers.

\n

* * *

\n

And… it bugs me how little real attention our industry pays to quality these days. In some cases the consequences are disastrous; in other cases they’re merely expensive. It doesn’t have to be this way.

\n

If it seems like I’m going too far with my definitions, well, I’m trying to bend the stick here.

"},"alternate":[{"href":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","type":"text/html"}],"crawled":1569829821629,"title":"On My Funny Ideas About What Beta Means","published":1567455823000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","fingerprint":"e526eb19","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98f:18991ffa","summary":{"direction":"ltr","content":"

I love this NetNewsWire write-up on WP Tavern.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","type":"text/html"}],"crawled":1569829821629,"published":1567280353000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","fingerprint":"57ea983b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98e:18991ffa","summary":{"direction":"ltr","content":"

NetNewsWire 5.0 is a 1.0 app in disguise.

\n

And so, as expected, we’ve had a ton of feature requests. Most people tend to request one or two features — and there’s a huge variety in these. People want different things.

\n

Nevertheless, there are a few themes we can pick out from what people are asking for:

\n
    \n
  • More syncing options, especially Feedly support
  • \n
  • iOS app
  • \n
  • Some way to deal with partial-content feeds
  • \n
  • Customization of the article pane (fonts, colors, etc.)
  • \n
  • Traditional view (timeline on top with single lines, article below)
  • \n
  • More sharing options (Instapaper, Pinboard, etc.)
  • \n
  • Customizable keyboard shortcuts
  • \n
  • State restoration
  • \n
  • Localizations
  • \n
  • Hiding read items in the timeline (or dimming them)
  • \n
  • Hiding feeds (in the sidebar) that have no unread articles
  • \n
  • User-created smart feeds
  • \n
\n

The less-common, more singular requests are for things like specific sorting options — there are lots of different small options that people would like.

\n

People have also asked for things that might surprise you (they surprised me) — for instance, we’ve had a request for monochrome icons for the toolbar. Another request for a Dark Mode that’s different from Apple’s Dark Mode. Etc.

\n

How We Choose What To Do Next

\n

The first principle is that we can’t lose what we love about the app. We do our damnedest to ship with no bugs, and the app needs to be fast and, most importantly, it needs to feel lighter-than-air.

\n

Whenever you add things — even if the app remains just as fast, even if there are no bugs — you still run the risk of losing that feeling of lightness. One of the quickest ways to lose that feeling is to add a whole bunch of preferences, View menu options, toolbar commands, and other chrome. So we’re going to be very slow to add things like that.

\n

NetNewsWire needs to not become fiddly. (Earlier versions of NetNewsWire got way too fiddly.)

\n

There are other questions we ask about a feature before we do it.

\n
    \n
  • Will it substantially benefit current users?
  • \n
  • Will it bring a number of new users to the app?
  • \n
  • Does the feature depend on something else being done first?
  • \n
  • How much work will it take?
  • \n
  • Does it require resources (such as new icons) that our programmers can’t provide?
  • \n
  • Does the feature really belong in an RSS reader at all?
  • \n
\n

And, because this is an open source app, there’s another dimension: people. Is someone available? Has someone just shown up who’s eager to work on a specific feature? Those things have an impact on scheduling, too.

\n

The good news is that most of the common feature requests are obvious things to do.

\n

Some examples — not nearly everything, just a few thoughts:

\n

The iOS app is in progress. Maurice Parker has been writing it, and it’s coming along very well. Still plenty more to do, and we won’t ship before iOS 13 ships, but it’s happening.

\n

Adding syncing options is a definite good thing for the app. Doing the first one (Feedbin) was the big effort, because it required building the infrastructure that makes syncing possible. Once that was done, adding additional services is not super-difficult. (Not easy, no. Nothing’s trivial. But at least the infrastructure and patterns are in place.)

\n

We’d like to support all the various services, or at least a majority of them. And we have people working on adding services.

\n

Customization of the article pane will most likely work the way it did in older versions of NetNewsWire: we had theme files which included templates and CSS. The app shipped with a few, and you could make your own and use themes other people made.

\n

This feature shipped with NetNewsWire 2.0, and people really loved it. It was fun!

\n

More sharing options is an obvious good idea. Of course you should be able to send to Instapaper, Pocket, Pinboard, and so on. We shipped with custom support for MarsEdit and the Micro.blog app — mainly because I use those apps. But an RSS reader ought to support as many sharing workflows as possible. That’s one of the core points of the app.

\n

* * *

\n

Anyway — the above doesn’t cover everything. Don’t take any of the above as gospel about what we’re doing or when, or what we’re not doing. We haven’t planned 5.1 yet! It’s too soon.

\n

There are also features that we want to do that people haven’t asked for, but that we think are cool. \uD83C\uDFB8

\n

The take-away from this article should be: we’re being very careful about designing and implementing new features, because we have to make sure NetNewsWire doesn’t lose what makes it special.

\n

But we are doing new features, because there are so many things that can make the app even better — we can make it better for current users and we can bring in new users.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5 Feature Requests","published":1567278518000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/29/follow_through","fingerprint":"444937b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98d:18991ffa","summary":{"direction":"ltr","content":"

Decades ago, when I was working for Dave Winer at UserLand, I learned about the concept of follow-through after a major release.

\n

If you’re an app maker, it might seem like your goal is to get to release day. Get the app done, make it available, publish an announcement, and then get back to coding. Let the world do what it’s going to do.

\n

One bang, and then back to work, in other words.

\n

But that’s not going to maximize your chances for a good release. You need to follow through — you need to keep going.

\n

Some of the things you might do, in no particular order:

\n
    \n
  • Publish tips on using your app — one a day or so
  • \n
  • Update your website with feedback, testimonials, and good reviews
  • \n
  • Be available and communicative about your app
  • \n
  • Go on some podcasts
  • \n
  • Write about how release day went
  • \n
  • Write about plans for the x.0.1 version
  • \n
  • Field bug reports and feature requests gratefully
  • \n
  • Thank reviewers who’ve done a good job
  • \n
  • Make it as easy as possible for reporters and reviewers to get access to your app and to you
  • \n
  • Work to build a community of customers, on Slack or similar
  • \n
\n

I’m sure you can think of more things to do — the above isn’t everything, and every app is different.

\n

But the key is that you don’t just do the release and then stop. Instead, show that you‘re responsive, show that your app has momentum, show that you care enough to keep showing up.

\n

For me, at least, this is the fun part. I realize that’s not true for everybody — but you should do it anyway. \uD83C\uDFA9

"},"alternate":[{"href":"https://inessential.com/2019/08/29/follow_through","type":"text/html"}],"crawled":1569829821629,"title":"Follow-Through","published":1567110304000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","fingerprint":"669e65c4","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98c:18991ffa","summary":{"direction":"ltr","content":"

We have a few reports of a crash where the add-feed-sheet window doesn’t load. There’s a line of code with window! — because of course we expect the window to have been loaded — and it crashes right there.

\n

This crash made zero sense to me, but Daniel Jalkut figured out the most likely cause and was able to reproduce it: it’s because the person has moved the app (from one folder to another) after launching it, while it’s running, and the nib-loading machinery can’t find the nib, because it’s moved along with the app.

\n

Tip: if you’re going to move an app, quit it first, then move it, and then re-launch it!

\n

At any rate: our fix for this will be to load that sheet on startup, and then recycle it on each use. This fix will go into NetNewsWire 5.0.1.

\n

This just fixes the bug with this one nib, though. A more systematic fix — maybe just a warning to the user suggesting they quit and re-launch — would be a good idea.

\n

File under “bugs iOS developers never have to worry about.” \uD83D\uDC07

\n

PS We have a 5.0.1 beta milestone now.

"},"alternate":[{"href":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","type":"text/html"}],"crawled":1569829821629,"title":"Daniel Figures Out One of the Two Crashing Bugs","published":1567022746000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/27/how_release_day_went","fingerprint":"2394a816","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98b:18991ffa","summary":{"direction":"ltr","content":"

Yesterday was a great day! A few things to note, in no particular order:

\n

NetNewsWire got some press coverage, including a well-done review in MacStories.

\n

We got a lot of feature requests, but no bug reports.

\n

Except that we did get a single-digit number of crash logs. On investigation, I found two distinct backtraces — we’ll need to fix those. The thing is, there’s no freakin’ way the app should crash in those spots. Except that, obviously, it can. Rarely, but it happens.

\n

The servers started timing-out at one point during the day. I contacted DreamHost support and they fixed things (and told me that the fixes they applied should prevent this in the future).

\n

There were a number of nice blog posts and tweets about NetNewsWire, which was awesome. After working so hard for so long, it’s great when people appreciate the app. We don’t get paid in money, after all. \uD83D\uDC23

\n

I have no idea how many downloads of the app there were. GitHub is hosting the download, via its releases feature, and I don’t see a way to find out how many times it’s been downloaded. Which is totally fine with me.

\n

* * *

\n

I should say something more about the no-bug-reports. There’s no special magic or talent or anything to this — there’s just the willingness to say that we’re not going to ship until we’ve got the bugs out, and then sticking to that.

\n

This is a matter of pride and ethics, for sure, but there’s another dimension: since the app is open source, it’s written by volunteers (including me), and we have no dedicated support team. Any time we spend fielding bug reports is time taken away from working on the next feature.

\n

Making apps — even, or especially, free apps — is an exercise in economics. With free apps, the economics are even more constrained, because nobody is going to hire even a part-time support person. So we do everything we can do keep costs down — especially time costs.

\n

Plus — buggy apps can be demoralizing to the people who work on them. Part of my job is to make sure people are proud and happy to work on the app. And that means making sure everyone knows we’re super-serious about doing our best to never ship bugs.

"},"alternate":[{"href":"https://inessential.com/2019/08/27/how_release_day_went","type":"text/html"}],"crawled":1569829821629,"title":"How Release Day Went","published":1566937707000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","fingerprint":"175d2cdb","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98a:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0 is shipping!

\n

In case you haven’t been following along until just now: NetNewsWire is an open source RSS reader for Mac. It’s free! You can just download it and use it. No strings.

\n

It’s designed to be stable, fast, and free of bugs. It doesn’t have a lot of features yet, and that’s because we prioritized quality over features. We will be adding more features, of course, but not quickly. We’re also working on an iOS app.

\n

It syncs using Feedbin. We’ll support more systems in the future (as many as possible).

\n

I hope you like it!

\n

Some links…

\n\n

Thanks to so many people

\n

I want to especially thank Sheila Simmons and my family and friends.

\n

This release took five years to make, and for four of those years it wasn’t even called NetNewsWire. It was just a year ago that I got the name NetNewsWire back from Black Pixel — and I thank them again for their wonderful generosity.

\n

I also want to thank Brad Ellis for making the beautiful app icon and toolbar icons. Thanks to our major code contributors: Maurice Parker, Olof Hellman, and Daniel Jalkut. Thanks to Ryan Dotson for writing the Help book. Thanks to Joe Heck for looking after infrastructure issues (especially continuous integration).

\n

Thanks to my co-workers and friends at The Omni Group (which is a wonderful place to work). Thanks to the ever-patient and ever-awesome NetNewsWire beta testers on the Slack group and elsewhere.

\n

And thanks to everyone who’s ever used the app in its 17-years-and-counting run. Because of you, NetNewsWire has been, and remains, the thrill of my career.

"},"alternate":[{"href":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0 Now Available","published":1566834451000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","fingerprint":"e30daaa8","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d989:18991ffa","summary":{"direction":"ltr","content":"

This is a little bit of bad news. It’s not my intention, and it’s not what I want to happen — but NetNewsWire 3.3.2 apparently does not launch in the next version of macOS (10.15, Catalina).

\n

It links to the PubSub framework, which is not included with the next macOS.

\n

NetNewsWire 3.3.2 was the last release of the full version that I worked on, before selling NetNewsWire to Black Pixel, and I’ve heard from lots of people that they’ve been using it ever since. They never switched.

\n

I would rather it continued working forever, but that’s not to be. Not my choice. Sorry about that!

"},"alternate":[{"href":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","type":"text/html"}],"crawled":1569829821629,"title":"End of the Line for NetNewsWire 3.3.2","published":1566515704000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","fingerprint":"c583e740","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d988:18991ffa","summary":{"direction":"ltr","content":"

The NetNewsWire blog has the details on NetNewsWire 5.0b5 — which should be the last beta.

\n

Still planning to do the 5.0 final release Monday morning, which really means doing the release on Sunday and pushing an announcement to this blog Monday morning. :)

\n

The last things on my to-do list are actually writing that announcement and doing screenshots for the NetNewsWire web page. Easy. \uD83D\uDC2F

"},"alternate":[{"href":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","type":"text/html"}],"crawled":1569829821629,"published":1566452581000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/20/immunization","fingerprint":"39a4bdb0","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d987:18991ffa","summary":{"direction":"ltr","content":"

Before every major release I like to try and think of everything mean that people might say about the app. It’s fun!

\n

So we just went through this exercise on the NetNewsWire Slack group. Here’s a taste:

\n
    \n
  • This took five years? I could write an RSS parser in a weekend.
  • \n
  • Can’t get my Twitter and Facebook feeds. Whatever.
  • \n
  • Doesn’t work with my Usenet host.
  • \n
  • The information density of the timeline is… lacking. What the hell.
  • \n
  • Not truly open source since it’s on a Mac.
  • \n
  • Not truly open source since it’s not GPL.
  • \n
  • No vim keys. Why bother.
  • \n
  • Regular people will never use an RSS reader. What’s the point?
  • \n
  • Brent’s last good idea was in 2002. Consider this a textbook case of coasting.
  • \n
  • Great app. Too bad RSS died with Google Reader.
  • \n
  • It totally didn’t pick up my subscriptions from the earlier version. How is this an upgrade?
  • \n
  • When does a 5.0 have fewer features than a 3.0? When it’s NetNewsWire.
  • \n
  • The echo chamber will love this app. They always do.
  • \n
  • Free app. Continues the race to the bottom. Pour one out for Silvio Rizzi.
  • \n
  • No way to send to Instapaper. Fuck it.
  • \n
  • Brent Simmons can’t stop pursuing a technology that even Google famously admitted was not worth bothering with.
  • \n
  • If this app took five years, imagine how long it will take before it will actually sync with Feedly.
  • \n
  • Sure it’s free, but I bet the Feedbin people paid them off, because the only way to sync is to pay money to Feedbin.
  • \n
  • No iCloud sync? Jerks.
  • \n
  • No iOS app. The revolution happened on mobile, Brant. What the actual fuck.
  • \n
  • Shoulda been Catalyst. Dinosaurs wrote this app.
  • \n
  • Not on the Mac App Store? I guess they don’t want users.
  • \n
  • I would totally use this if it had just this one [feature x], which I can’t believe they shipped without. (Multiply this comment by 100, with a different feature x each time.)
  • \n
  • Area Man Can’t Let RSS Go
  • \n
\n

Some feedback will be factually inaccurate, but we like to imagine that too:

\n
    \n
  • I remember using NetNewsWire on OS 9, and it hasn’t really improved since then. They should make it a Cocoa app.
  • \n
  • Doesn’t work with web comics. POS
  • \n
  • Doesn’t support 10.5.
  • \n
  • It should be free.
  • \n
  • You’d think they would have updated the design — but it looks exactly like NetNewsWire of old.
  • \n
  • Why the hell would they build on that aging code base from Black Pixel? I heard it doesn’t even use ARC.
  • \n
  • No way to sync? What’s their actual problem?
  • \n
\n

See? The actual feedback will be nicer than the stuff we thought up. This provides a bit of immunization. :)

\n

But, also, there will be negative feedback we didn’t imagine. That’s the gold!

\n

* * *

\n

Bonus from Daniel Jalkut, but not actually a criticism:

\n
\n

Can’t innovate, my RSS.

\n
"},"alternate":[{"href":"https://inessential.com/2019/08/20/immunization","type":"text/html"}],"crawled":1569829821629,"title":"Immunization","published":1566332363000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","fingerprint":"592043f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d986:18991ffa","summary":{"direction":"ltr","content":"

I think we’re still on track for releasing NetNewsWire 5.0 Monday, August 26. There will be one more beta before then.

\n

I’ll be available for podcasts, interviews-via-email, etc. If you’d like to set something up, email me or DM me on Twitter.

"},"alternate":[{"href":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","type":"text/html"}],"crawled":1569829821629,"published":1566259329000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlyResourceIdTests.swift b/Frameworks/Account/AccountTests/Feedly/FeedlyResourceIdTests.swift new file mode 100644 index 000000000..f1f9da824 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/FeedlyResourceIdTests.swift @@ -0,0 +1,27 @@ +// +// FeedlyResourceIdTests.swift +// AccountTests +// +// Created by Kiel Gillard on 3/10/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import XCTest +@testable import Account + +class FeedlyResourceIdTests: XCTestCase { + + func testFeedResourceId() { + let expectedUrl = "http://ranchero.com/blog/atom.xml" + + let feedResource = FeedlyFeedResourceId(id: "feed/\(expectedUrl)") + let urlResource = FeedlyFeedResourceId(id: expectedUrl) + let otherResource = FeedlyFeedResourceId(id: "whiskey/\(expectedUrl)") + let invalidResource = FeedlyFeedResourceId(id: "") + + XCTAssertEqual(feedResource.url, expectedUrl) + XCTAssertEqual(urlResource.url, expectedUrl) + XCTAssertEqual(otherResource.url, otherResource.id) + XCTAssertEqual(invalidResource.url, invalidResource.id) + } +} diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/feedly_collections_initial.json b/Frameworks/Account/AccountTests/Feedly/Initial/feedly_collections_initial.json new file mode 100644 index 000000000..c42b724d6 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/feedly_collections_initial.json @@ -0,0 +1 @@ +[{"customizable":true,"feeds":[{"feedId":"feed/http://tidbits.com/feeds/tidbits_blurb.rss","id":"feed/http://tidbits.com/feeds/tidbits_blurb.rss","title":"TidBITS: Apple News for the Rest of Us","updated":1569622634000,"velocity":6.8,"subscribers":1,"website":"https://tidbits.com","language":"en","description":"Thoughtful, detailed coverage of everything Apple for 29 years\nand the TidBITS Content Network for Apple professionals"},{"feedId":"feed/http://www.macalope.com/feed/","id":"feed/http://www.macalope.com/feed/","title":"Macalope","updated":1498941877000,"velocity":0.0,"subscribers":1,"website":"http://www.macalope.com","language":"en","state":"dormant","description":"Full of sound and furry"},{"feedId":"feed/http://flyingmeat.com/blog/atom.xml","id":"feed/http://flyingmeat.com/blog/atom.xml","title":"The Flying Meat Weblog","updated":1343168154000,"velocity":0.0,"subscribers":1,"website":"https://flyingmeat.com/blog/","language":"en","state":"dormant"},{"feedId":"feed/http://www.macdrifter.com/feeds/all.atom.xml","id":"feed/http://www.macdrifter.com/feeds/all.atom.xml","title":"Macdrifter","updated":1561539243000,"velocity":0.1,"subscribers":1,"website":"http://www.macdrifter.com/","language":"en"},{"feedId":"feed/http://9to5mac.com/feed/","id":"feed/http://9to5mac.com/feed/","title":"9to5Mac","updated":1569829982898,"velocity":22.6,"subscribers":2,"website":"https://9to5mac.com","language":"en","description":"Apple News & Mac Rumors Breaking All Day"}],"label":"Macintosh","created":1569829941677,"enterprise":false,"numFeeds":5,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815"},{"customizable":false,"feeds":[],"label":"Must Read","created":1569417923847,"enterprise":false,"numFeeds":0,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must"},{"customizable":true,"feeds":[{"feedId":"feed/http://www.codebykevin.com/blosxom.cgi/index.rss","id":"feed/http://www.codebykevin.com/blosxom.cgi/index.rss","title":"Code by Kevin","updated":1564803480000,"velocity":0.1,"subscribers":1,"website":"https://www.codebykevin.com/blosxom.cgi","language":"en","description":"Programming, code, business, and other pursuits"},{"feedId":"feed/http://www.raywenderlich.com/feed","id":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich","updated":1569591049000,"velocity":4.5,"subscribers":1,"language":"en"},{"feedId":"feed/http://ddeville.me/feed.xml","id":"feed/http://ddeville.me/feed.xml","title":"Damien DeVille","updated":1454187600000,"velocity":0.0,"subscribers":1,"website":"http://ddeville.me","language":"en","state":"dormant","description":"Software engineer at Dropbox. Previously at Realmac Software. UCL Computer Science alumnus."},{"feedId":"feed/http://owensd.io/rss.xml","id":"feed/http://owensd.io/rss.xml","title":"owensd.io - thoughts in and out - Articles","updated":1534837458000,"velocity":0.0,"subscribers":1,"website":"https://owensd.io","language":"en","state":"dormant","description":"A builder of things."},{"feedId":"feed/http://prog21.dadgum.com/atom.xml","id":"feed/http://prog21.dadgum.com/atom.xml","title":"Programming in the 21st Century","updated":1483509600000,"velocity":0.0,"subscribers":1,"website":"http://prog21.dadgum.com/","language":"en","state":"dormant"},{"feedId":"feed/http://feeds.feedburner.com/alistapart/main","id":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","updated":1569829877486,"velocity":4.5,"subscribers":1,"website":"https://alistapart.com","language":"en","description":"Articles for people who make web sites."},{"feedId":"feed/http://www.russbishop.net/feed","id":"feed/http://www.russbishop.net/feed","title":"Russ Bishop (atom)","updated":1551122826000,"velocity":0.0,"subscribers":1,"website":"http://www.russbishop.net/feed","language":"en","state":"dormant","description":"This blog represents my own personal opinion and is not endorsed by my employer."},{"feedId":"feed/http://mentalfaculty.tumblr.com/rss","id":"feed/http://mentalfaculty.tumblr.com/rss","title":"The Mental Blog","updated":1468311749000,"velocity":0.0,"subscribers":1,"website":"https://mentalfaculty.tumblr.com/","language":"en","state":"dormant","description":"Drew McCormack (@drewmccormack) is founder of The Mental Faculty, developer of Mental Case and the Ensembles sync framework"},{"feedId":"feed/http://blog.amyworrall.com/rss","id":"feed/http://blog.amyworrall.com/rss","title":"What Amy Did","updated":1486165238000,"velocity":0.0,"subscribers":1,"website":"https://blog.amyworrall.com/","language":"en","state":"dormant","description":"I’m a software developer from Coventry. I care about design and user experience. \n\nFollow me on Twitter: @amyruthworrall"},{"feedId":"feed/http://oleb.net/blog/atom.xml","id":"feed/http://oleb.net/blog/atom.xml","title":"Ole Begemann: iOS Development","updated":1559322747743,"velocity":0.1,"subscribers":1,"website":"https://oleb.net/blog/","language":"en"},{"feedId":"feed/http://subjc.com/atom.xml","id":"feed/http://subjc.com/atom.xml","title":"Subjective-C","updated":1461251700000,"velocity":0.0,"subscribers":2,"website":"http://subjc.com/","language":"en","state":"dormant"},{"feedId":"feed/http://robnapier.net/atom.xml","id":"feed/http://robnapier.net/atom.xml","title":"Cocoaphony","updated":1558929600000,"velocity":0.1,"subscribers":1,"website":"https://robnapier.net/","language":"en"},{"feedId":"feed/http://cocoamanifest.net/feeds/index.xml","id":"feed/http://cocoamanifest.net/feeds/index.xml","title":"Cocoa Manifest","updated":1402642440000,"velocity":0.0,"subscribers":2,"website":"http://cocoamanifest.net","language":"en","state":"dormant"},{"feedId":"feed/http://petersteinberger.com/atom.xml","id":"feed/http://petersteinberger.com/atom.xml","title":"Peter Steinberger","updated":1438088460000,"velocity":0.0,"subscribers":1,"website":"http://petersteinberger.com/","language":"en","state":"dormant"},{"feedId":"feed/http://www.weheartswift.com/feed/","id":"feed/http://www.weheartswift.com/feed/","title":"We ❤ Swift","updated":1569829929574,"velocity":0.2,"subscribers":1,"website":"https://www.weheartswift.com","language":"en","description":"Swift Tutorials and iOS development"},{"feedId":"feed/https://medium.com/feed/swift-programming","id":"feed/https://medium.com/feed/swift-programming","title":"Swift Programming — Medium","updated":1554317410000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/swift-programming?source=rss----5396e0e8bc29---4","language":"en","description":"The Swift Programming Language - Medium"},{"feedId":"feed/http://tonyarnold.com/atom.xml","id":"feed/http://tonyarnold.com/atom.xml","title":"The blog of Tony Arnold","updated":1531312200000,"velocity":0.0,"subscribers":1,"website":"https://tonyarnold.com","language":"en","state":"dormant"},{"feedId":"feed/http://borkware.com/miniblog/rss/rss.xml","id":"feed/http://borkware.com/miniblog/rss/rss.xml","title":"Borkware Miniblog","updated":1360542004000,"velocity":0.0,"subscribers":1,"website":"https://borkwarellc.wordpress.com","language":"en","state":"dormant","description":"Bork bork bork bork."},{"feedId":"feed/http://nshipster.com/feed.xml","id":"feed/http://nshipster.com/feed.xml","title":"NSHipster","updated":1569418049005,"velocity":0.9,"subscribers":2,"website":"https://nshipster.com/","language":"en","description":"NSHipster is a journal of the overlooked bits in Objective-C, Swift, and Cocoa. Updated weekly."},{"feedId":"feed/http://feeds.feedburner.com/pilkyme","id":"feed/http://feeds.feedburner.com/pilkyme","title":"Pilky.me","updated":1561420800000,"velocity":0.1,"subscribers":1,"website":"https://pilky.me/","language":"en"},{"feedId":"feed/http://macoscope.com/blog/feed/","id":"feed/http://macoscope.com/blog/feed/","title":"[macoscope blog]","updated":1467803080000,"velocity":0.0,"subscribers":1,"website":"http://macoscope.com/blog","language":"en","state":"dormant","description":"The Macoscope Team on Designing and Developing Apps"},{"feedId":"feed/http://iosunittesting.com/feed/","id":"feed/http://iosunittesting.com/feed/","title":"iOS Unit Testing","updated":1436534796000,"velocity":0.0,"subscribers":1,"website":"http://iosunittesting.com","language":"en","state":"dormant","description":"It's about TDD, unit testing, and creating bug free code on iOS."},{"feedId":"feed/http://airspeedvelocity.net/feed/","id":"feed/http://airspeedvelocity.net/feed/","title":"Airspeed Velocity","updated":1452449244000,"velocity":0.0,"subscribers":1,"website":"https://airspeedvelocity.net","language":"en","state":"dormant","description":"African or European Swift?"},{"feedId":"feed/http://www.cimgf.com/feed/","id":"feed/http://www.cimgf.com/feed/","title":"Cocoa Is My Girlfriend","updated":1525988390000,"velocity":0.0,"subscribers":1,"website":"http://www.cimgf.com","language":"en","state":"dormant","description":"Taglines are for Windows programmers"},{"feedId":"feed/http://confusatory.org/rss","id":"feed/http://confusatory.org/rss","title":"The Confusatory","updated":1476982283000,"velocity":0.0,"subscribers":1,"website":"https://confusatory.org/","language":"en","state":"dormant","description":"cbowns’s tumblr."},{"feedId":"feed/http://indiestack.com/feed/","id":"feed/http://indiestack.com/feed/","title":"Indie Stack","updated":1569829987669,"velocity":0.2,"subscribers":1,"website":"https://indiestack.com","language":"en","description":"Hacking the Mac, iOS, and more with Daniel Jalkut"}],"label":"Programming","created":1569829874442,"enterprise":false,"numFeeds":26,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5"},{"customizable":true,"feeds":[{"feedId":"feed/http://feedpress.me/sixcolors","id":"feed/http://feedpress.me/sixcolors","title":"Six Colors","updated":1569800417902,"velocity":23.7,"subscribers":1,"website":"https://www.sixcolors.com/","language":"en","description":"Writing about Apple and other stuff by Jason Snell, Dan Moren, and others."},{"feedId":"feed/http://corinnekrych.blogspot.com/feeds/posts/default","id":"feed/http://corinnekrych.blogspot.com/feeds/posts/default","title":"chat & code","updated":1547807580000,"velocity":0.0,"subscribers":1,"website":"http://corinnekrych.blogspot.com/","language":"en","state":"dormant","description":"Code is craft and collaboration is key to success. I love chatting the latest tech trends at coffee break: female geek."},{"feedId":"feed/http://ericasadun.com/feed/","id":"feed/http://ericasadun.com/feed/","title":"Erica Sadun","updated":1569452593000,"velocity":0.7,"subscribers":1,"website":"https://ericasadun.com","language":"en","description":"Where technology meets something or other"},{"feedId":"feed/http://therecord.co/xml/rss.xml","id":"feed/http://therecord.co/xml/rss.xml","title":"The Record","updated":1401364800000,"velocity":0.0,"subscribers":1,"website":"http://therecord.co/","language":"en","state":"dormant","description":"The stories you should know about the Mac and Cocoa developer community. Hosted by Brent Simmons and Chris Parrish."},{"feedId":"feed/https://grokswift.com/feed/index.xml","id":"feed/https://grokswift.com/feed/index.xml","title":"Grok Swift","updated":1527175834000,"velocity":0.0,"subscribers":1,"website":"https://grokswift.com/","language":"en","state":"dormant"},{"feedId":"feed/https://blog.alltheflow.com/rss/","id":"feed/https://blog.alltheflow.com/rss/","title":"All The Flow","updated":1551711655000,"velocity":0.0,"subscribers":1,"website":"http://blog.alltheflow.com/","language":"en","state":"dormant","description":"Cocoa, Swift, tools, Auto Layout - with 🧡"},{"feedId":"feed/http://onefoottsunami.com/feed/atom/","id":"feed/http://onefoottsunami.com/feed/atom/","title":"One Foot Tsunami","updated":1569595706000,"velocity":4.5,"subscribers":1,"website":"https://onefoottsunami.com","language":"en","description":"Slightly less disappointing than it sounds"},{"feedId":"feed/http://www.loopinsight.com/feed/","id":"feed/http://www.loopinsight.com/feed/","title":"Loop Insight","updated":1569789035000,"velocity":4.5,"subscribers":1,"website":"https://www.loopinsight.com","language":"en","description":"Making Sense of Technology"},{"feedId":"feed/http://beckyhansmeyer.com/feed/","id":"feed/http://beckyhansmeyer.com/feed/","title":"Becky Hansmeyer","updated":1569087367000,"velocity":1.4,"subscribers":1,"website":"https://beckyhansmeyer.com","language":"en","description":"100% grass-fed Swift"},{"feedId":"feed/http://designatednerd.com/feed/","id":"feed/http://designatednerd.com/feed/","title":"Designated Nerd","updated":1564425470000,"velocity":0.1,"subscribers":1,"website":"http://designatednerd.com","language":"en","description":"Software and Technical Support"},{"feedId":"feed/http://appcamp4girls.com/blog?format=RSS","id":"feed/http://appcamp4girls.com/blog?format=RSS","title":"Blog - App Camp For Girls","updated":1558129314000,"velocity":0.1,"subscribers":1,"website":"https://appcamp4girls.com/blog/","language":"en"},{"feedId":"feed/https://inspiredmouse.com/feed/","id":"feed/https://inspiredmouse.com/feed/","title":"Inspired Mouse","updated":1511986957000,"velocity":0.0,"subscribers":1,"website":"https://inspiredmouse.com","language":"en","state":"dormant","description":"No project is too diminutive"},{"feedId":"feed/https://incrementalistblog.wordpress.com/feed/","id":"feed/https://incrementalistblog.wordpress.com/feed/","title":"The Incrementalist.","updated":1449808490000,"velocity":0.0,"subscribers":1,"website":"https://incrementalistblog.wordpress.com","language":"en","state":"dormant","description":"notes on design"},{"feedId":"feed/http://www.virginiaroberts.com/feed/","id":"feed/http://www.virginiaroberts.com/feed/","title":"Virginia Roberts","updated":1549571065000,"velocity":0.0,"subscribers":1,"website":"http://www.virginiaroberts.com","language":"en","state":"dormant"},{"feedId":"feed/http://jessysaurusrex.com/feed/","id":"feed/http://jessysaurusrex.com/feed/","title":"jessysaurusrex","updated":1521816327000,"velocity":0.0,"subscribers":1,"website":"https://jessysaurusrex.com","language":"en","state":"dormant","description":"I wear big necklaces and (attempt to) hack things."},{"feedId":"feed/http://blog.nicoleblee.com/feed/","id":"feed/http://blog.nicoleblee.com/feed/","title":"scattered thoughts","updated":1397441482000,"velocity":0.0,"subscribers":1,"website":"http://blog.nicoleblee.com","language":"en","state":"dormant","description":"(a collection of essays and other writings)"},{"feedId":"feed/https://medium.com/feed/@tessr","id":"feed/https://medium.com/feed/@tessr","title":"Tess Rinearson on Medium","updated":1501016354000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@tessr?source=rss-c16152863954------2","language":"en","state":"dormant","description":"Stories by Tess Rinearson on Medium"},{"feedId":"feed/https://medium.com/feed/@emarley","id":"feed/https://medium.com/feed/@emarley","title":"Liz Marley on Medium","updated":1514047622000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@emarley?source=rss-b4981c59ffa5------2","language":"en","state":"dormant","description":"Stories by Liz Marley on Medium"},{"feedId":"feed/http://blog.cocoabythefire.com/rss","id":"feed/http://blog.cocoabythefire.com/rss","title":"cocoa by the fire","updated":1488945726000,"velocity":0.0,"subscribers":1,"website":"https://blog.cocoabythefire.com/","language":"en","state":"dormant","description":"Hey there, I’m Brit! Coder, Entrepreneur, Daydreamer, Lucky Wife and Mom."},{"feedId":"feed/http://www.catehuston.com/blog/feed/","id":"feed/http://www.catehuston.com/blog/feed/","title":"Accidentally in Code","updated":1567641655000,"velocity":0.2,"subscribers":1,"website":"https://cate.blog","language":"en","description":"Engineering an Interesting Life"},{"feedId":"feed/http://www.aleenmean.com/feed.xml","id":"feed/http://www.aleenmean.com/feed.xml","title":"Aleen Mean","updated":1562374146000,"velocity":0.1,"subscribers":1,"website":"https://aleenmean.com/","language":"en","description":"Technology, diversity, and miscellaneous musings by Aleen Simms."},{"feedId":"feed/http://redqueencoder.com/feed/","id":"feed/http://redqueencoder.com/feed/","title":"The Red Queen Coder","updated":1547225214000,"velocity":0.0,"subscribers":1,"website":"http://redqueencoder.com","language":"en","state":"dormant","description":"If you give a person a program, you'll frustrate him for a day. If you teach a person to program, you will frustrate them for a lifetime!"},{"feedId":"feed/http://meaganwaller.com/index.php/feed/","id":"feed/http://meaganwaller.com/index.php/feed/","title":"Meagan Waller","updated":1403205537000,"velocity":0.0,"subscribers":1,"website":"https://meaganwaller.com","language":"en","state":"dormant"},{"feedId":"feed/http://www.myballard.com/feed/","id":"feed/http://www.myballard.com/feed/","title":"Ballard","updated":1569614203000,"velocity":6.8,"subscribers":1,"website":"https://www.myballard.com","language":"en","description":"News, events and restaurants for Seattle's Ballard and Fremont neighborhoods"},{"feedId":"feed/https://kateheddleston.com/blog/feed.atom","id":"feed/https://kateheddleston.com/blog/feed.atom","title":"KateHeddleston.com Blog Posts","updated":1526573156000,"velocity":0.0,"subscribers":1,"website":"https://www.kateheddleston.com/blog","language":"en","state":"dormant"},{"feedId":"feed/http://blog.erynwells.me/rss","id":"feed/http://blog.erynwells.me/rss","title":"Eryn Wells","updated":1442175535000,"velocity":0.0,"subscribers":1,"website":"https://blog.erynwells.me/","language":"en","state":"dormant"},{"feedId":"feed/http://lambdamaphone.blogspot.com/feeds/posts/default","id":"feed/http://lambdamaphone.blogspot.com/feeds/posts/default","title":"Everything in Context","updated":1519599000002,"velocity":0.0,"subscribers":1,"website":"http://lambdamaphone.blogspot.com/","language":"en","state":"dormant","description":"Game design, programming languages, and academia."},{"feedId":"feed/http://inessential.com/xml/rss.xml","id":"feed/http://inessential.com/xml/rss.xml","title":"Inessential","updated":1569829821629,"velocity":3.8,"subscribers":1,"website":"https://inessential.com/","language":"en","description":"Brent Simmons’s weblog."},{"feedId":"feed/http://nothe.purplellamas.net/index.xml","id":"feed/http://nothe.purplellamas.net/index.xml","title":"Blog Posts About Stuff","updated":1512518400000,"velocity":0.0,"subscribers":1,"website":"http://nothe.purplellamas.net/","language":"en","state":"dormant","description":"Recent content on Blog Posts About Stuff"},{"feedId":"feed/http://www.mostgood.net/blog?format=RSS","id":"feed/http://www.mostgood.net/blog?format=RSS","title":"mostgood","updated":1433114183000,"velocity":0.0,"subscribers":1,"website":"http://www.mostgood.net/","language":"en","state":"dormant"},{"feedId":"feed/http://www.mistys-internet.website/blog/atom.xml","id":"feed/http://www.mistys-internet.website/blog/atom.xml","title":"The Future Is Now","updated":1550722972000,"velocity":0.0,"subscribers":1,"website":"http://mistys-internet.website/blog/","language":"en","state":"dormant"},{"feedId":"feed/http://pewpewthespells.com/feed.xml","id":"feed/http://pewpewthespells.com/feed.xml","title":"Samantha Marshall's Blog","updated":1569829870614,"velocity":8.6,"subscribers":1,"website":"https://pewpewthespells.com/","language":"en","description":"Blog Feed"},{"feedId":"feed/http://blog.ashleynh.me/rss/","id":"feed/http://blog.ashleynh.me/rss/","title":"Ashley Nelson-Hornstein","updated":1496606482000,"velocity":0.0,"subscribers":1,"website":"http://ashleynh.me:80/","language":"en","state":"dormant","description":"Ashley Nelson"},{"feedId":"feed/https://medium.com/feed/@nerdonica","id":"feed/https://medium.com/feed/@nerdonica","title":"Veronica Ray on Medium","updated":1471184706000,"velocity":0.0,"subscribers":1,"website":"https://medium.com/@nerdonica?source=rss-eaf18ccd367f------2","language":"en","state":"dormant","description":"Stories by Veronica Ray on Medium"},{"feedId":"feed/http://www.nadynerichmond.com/blog/feed/","id":"feed/http://www.nadynerichmond.com/blog/feed/","title":"go ahead, mac my day","updated":1506105463000,"velocity":0.0,"subscribers":1,"website":"http://www.nadynerichmond.com/blog","language":"en","state":"dormant","description":"a Macintosh girl in a Microsoft world"},{"feedId":"feed/http://www.bbc.co.uk/blogs/doctorwho/rss","id":"feed/http://www.bbc.co.uk/blogs/doctorwho/rss","title":"Doctor Who","updated":1567440000000,"velocity":0.2,"subscribers":1,"website":"https://www.bbc.co.uk/blogs/doctorwho","language":"en","description":"All the latest news and features from the world of Doctor Who."},{"feedId":"feed/http://scripting.com/rss.xml","id":"feed/http://scripting.com/rss.xml","title":"Scripting News","updated":1569783112000,"velocity":11.3,"subscribers":1,"website":"http://scripting.com/","language":"en","description":"Scripting News, the weblog started in 1994 that bootstrapped the blogging revolution. 🚀"},{"feedId":"feed/http://daringfireball.net/feeds/main","id":"feed/http://daringfireball.net/feeds/main","title":"Daring Fireball","updated":1569814548000,"velocity":10.8,"subscribers":1,"website":"https://daringfireball.net/","language":"en","description":"By John Gruber"},{"feedId":"feed/http://www.mechanicalgirl.com/feeds/all/","id":"feed/http://www.mechanicalgirl.com/feeds/all/","title":"MechanicalGirl","updated":1569829801167,"velocity":1.1,"subscribers":1,"website":"http://www.MechanicalGirl.com/","language":"en","description":"Latest posts on MechanicalGirl"},{"feedId":"feed/http://ranchero.com/xml/rss.xml","id":"feed/http://ranchero.com/xml/rss.xml","title":"ranchero.com","updated":1569437386000,"velocity":2.0,"subscribers":1,"website":"https://inessential.com/","language":"en","description":"Brent Simmons’s weblog."},{"feedId":"feed/http://natashatherobot.com/feed/","id":"feed/http://natashatherobot.com/feed/","title":"Natasha The Robot","updated":1569829929101,"velocity":0.2,"subscribers":2,"website":"https://www.natashatherobot.com","language":"en"},{"feedId":"feed/http://daringfireball.net/index.xml","id":"feed/http://daringfireball.net/index.xml","title":"Daring Fireball","updated":1569816524045,"velocity":13.5,"subscribers":6,"website":"https://daringfireball.net/","language":"en","description":"By John Gruber"},{"feedId":"feed/http://timekl.com/atom.xml","id":"feed/http://timekl.com/atom.xml","title":"don't panic","updated":1555225200000,"velocity":0.0,"subscribers":1,"website":"https://timekl.com/","language":"en","description":"Occasional posts, usually about technology"},{"feedId":"feed/http://nataliepo.typepad.com/nataliepo/rss.xml","id":"feed/http://nataliepo.typepad.com/nataliepo/rss.xml","title":"nataliepo (posts on 'nataliepo' (rss 2.0))","updated":1447256454000,"velocity":0.0,"subscribers":1,"website":"https://nataliepo.typepad.com/nataliepo/","language":"en","state":"dormant"},{"feedId":"feed/http://shapeof.com/rss.xml","id":"feed/http://shapeof.com/rss.xml","title":"The Shape of Everything","updated":1569263479000,"velocity":1.6,"subscribers":1,"website":"https://shapeof.com/","language":"en","description":"A website mostly about Mac stuff, written by Gus Mueller"},{"feedId":"feed/http://jvns.ca/atom.xml","id":"feed/http://jvns.ca/atom.xml","title":"Julia Evans","updated":1569829745957,"velocity":3.8,"subscribers":2,"website":"http://jvns.ca","language":"en"},{"feedId":"feed/https://www.natashatherobot.com/feed/","id":"feed/https://www.natashatherobot.com/feed/","title":"Natasha the Robot","updated":1545498499000,"velocity":0.0,"subscribers":1,"website":"https://www.natashatherobot.com","language":"en","state":"dormant"},{"feedId":"feed/http://pointersgonewild.com/feed/","id":"feed/http://pointersgonewild.com/feed/","title":"Pointers Gone Wild","updated":1560168135000,"velocity":0.1,"subscribers":1,"website":"https://pointersgonewild.com","language":"en","description":"A blog about compilers, programming and technology."},{"feedId":"feed/http://www.kristinathai.com/feed/","id":"feed/http://www.kristinathai.com/feed/","title":"kristinathai.com","updated":1563831802000,"velocity":0.1,"subscribers":1,"website":"http://www.kristinathai.com","language":"ja"},{"feedId":"feed/https://developer.apple.com/swift/blog/news.rss","id":"feed/https://developer.apple.com/swift/blog/news.rss","title":"Swift Blog - Apple Developer","updated":1476306000000,"velocity":0.0,"subscribers":1,"website":"https://developer.apple.com/swift/blog/","language":"en","state":"dormant","description":"Get the latest news and helpful tips on the Swift programming language from the engineers who created it."},{"feedId":"feed/http://www.rebeccamiller-webster.com/feed/","id":"feed/http://www.rebeccamiller-webster.com/feed/","title":"Rebecca Miller-Webster","updated":1547836736000,"velocity":0.0,"subscribers":1,"website":"https://www.rebeccamiller-webster.com","language":"en","state":"dormant","description":"Ruby + JavaScript"},{"feedId":"feed/http://swift.ayaka.me/posts?format=RSS","id":"feed/http://swift.ayaka.me/posts?format=RSS","title":"Learn Swift ↯","updated":1466314290000,"velocity":0.0,"subscribers":1,"website":"http://swift.ayaka.me/","language":"en","state":"dormant"},{"feedId":"feed/http://www.imore.com/rss.xml","id":"feed/http://www.imore.com/rss.xml","title":"iMore","updated":1569826800000,"velocity":6.8,"subscribers":1,"website":"https://www.imore.com/","language":"en","description":"More news and rumors, more help and how-tos, more app and accessory reviews, more iPhone and iPad and iPod touch. More of everything you love. iMore."},{"feedId":"feed/http://blog.thoughtbrain.com/feed/","id":"feed/http://blog.thoughtbrain.com/feed/","title":"Feed: Thoughtbrain Bloggers","updated":1426140251000,"velocity":0.0,"subscribers":1,"website":"http://blog.thoughtbrain.com","language":"en","state":"dormant","description":"Designery nerdy things."},{"feedId":"feed/http://blog.ellenchisa.com/feed/","id":"feed/http://blog.ellenchisa.com/feed/","title":"Ellen's Blog","updated":1546640740000,"velocity":0.0,"subscribers":1,"website":"https://blog.ellenchisa.com?source=rss----da542b929da2---4","language":"en","state":"dormant","description":"I’m starting a new company with @paulbiggar, which you can learn about at https://darklang.com. I mostly write about startups and software development. - Medium"},{"feedId":"feed/https://medium.com/feed/@jaimeejaimee","id":"feed/https://medium.com/feed/@jaimeejaimee","title":"jaimeejaimee","updated":1558376026000,"velocity":0.1,"subscribers":1,"website":"https://medium.com/@jaimeejaimee?source=rss-11d5cc4494a2------2","language":"en","description":"Stories by jaimeejaimee on Medium"}],"label":"Uncategorized","created":1569829699432,"enterprise":false,"numFeeds":56,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8"},{"customizable":true,"feeds":[{"feedId":"feed/http://bryan.io/rss","id":"feed/http://bryan.io/rss","title":"bryan i/o","updated":1567230243000,"velocity":0.2,"subscribers":1,"website":"https://bryan.io/","language":"en","description":"Software engineer who led iOS at Tumblr from 2012-2015. Mostly cheeseburgers at this point. Over at irace.me nowadays."},{"feedId":"feed/http://nickbradbury.com/feed/","id":"feed/http://nickbradbury.com/feed/","title":"Nick Bradbury","updated":1503946502000,"velocity":0.0,"subscribers":1,"website":"https://nickbradbury.com","language":"en","state":"dormant","description":"I develop Android apps. In a previous life I created HomeSite, TopStyle and FeedDemon for Windows."},{"feedId":"feed/http://feeds.feedburner.com/domainofthebored","id":"feed/http://feeds.feedburner.com/domainofthebored","title":"Peter Hosey","updated":1567916255000,"velocity":0.2,"subscribers":1,"website":"https://boredzo.org/blog","language":"en","description":"The personal weblog of Peter Hosey."},{"feedId":"feed/http://blog.metaobject.com/feeds/posts/default","id":"feed/http://blog.metaobject.com/feeds/posts/default","title":"metablog","updated":1556396880001,"velocity":0.0,"subscribers":1,"website":"https://blog.metaobject.com/","language":"en"},{"feedId":"feed/http://feeds2.feedburner.com/adobe/jnack","id":"feed/http://feeds2.feedburner.com/adobe/jnack","title":"John Nack on Adobe (rss (feedburner))","updated":1391820146000,"velocity":0.0,"subscribers":1,"website":"http://blogs.adobe.com/jnack","language":"en","state":"dormant"},{"feedId":"feed/http://www.zathras.de/angelweb/BlogRSSFeed.rss","id":"feed/http://www.zathras.de/angelweb/BlogRSSFeed.rss","title":"Zathras.de - Uli's most useless blog in the World","updated":1562364000000,"velocity":0.1,"subscribers":1,"website":"https://orangejuiceliberationfront.com/","language":"en","description":"Uli's blog on programming, game development, pop culture and other boring things."},{"feedId":"feed/http://typesetinthefuture.com/feed/","id":"feed/http://typesetinthefuture.com/feed/","title":"Typeset In The Future","updated":1544536375000,"velocity":0.0,"subscribers":1,"website":"https://typesetinthefuture.com","language":"en","state":"dormant","description":"Typography and Design in Science Fiction Movies"},{"feedId":"feed/http://david-smith.org/atom.xml","id":"feed/http://david-smith.org/atom.xml","title":"David Smith","updated":1569830048565,"velocity":0.5,"subscribers":1,"website":"http://david-smith.org/","language":"en"},{"feedId":"feed/http://awkwardhare.com/rss","id":"feed/http://awkwardhare.com/rss","title":"Awkward Hare","updated":1484149569000,"velocity":0.0,"subscribers":1,"website":"https://awkwardhare.com/","language":"en","state":"dormant","description":"A blog by Greg Pierce"},{"feedId":"feed/http://frozendevil.com/atom.xml","id":"feed/http://frozendevil.com/atom.xml","title":"frozendevil","updated":1398927600000,"velocity":0.0,"subscribers":1,"website":"http://frozendevil.com/","language":"en","state":"dormant"},{"feedId":"feed/http://useyourloaf.com/blog/rss.xml","id":"feed/http://useyourloaf.com/blog/rss.xml","title":"Use Your Loaf","updated":1569830765949,"velocity":0.5,"subscribers":2,"website":"https://useyourloaf.com/blog/","language":"en","description":"Recent content on Use Your Loaf - iOS Development News & Tips"},{"feedId":"feed/http://www.appleoutsider.com/feed/","id":"feed/http://www.appleoutsider.com/feed/","title":"Apple Outsider","updated":1402413759000,"velocity":0.0,"subscribers":1,"website":"https://www.appleoutsider.com","language":"en","state":"dormant"},{"feedId":"feed/http://rathole.tumblr.com/rss","id":"feed/http://rathole.tumblr.com/rss","title":"RatHole","updated":1554476924000,"velocity":0.0,"subscribers":1,"website":"https://rathole.tumblr.com/","language":"en","description":"what my brain does when I’m not looking"},{"feedId":"feed/http://codeplease.io/rss/","id":"feed/http://codeplease.io/rss/","title":"Codeplease","updated":1511101649000,"velocity":0.2,"subscribers":1,"website":"http://codeplease.io/","language":"en","state":"dormant","description":"Ramblings about code"},{"feedId":"feed/http://blog.jaredsinclair.com/rss?1","id":"feed/http://blog.jaredsinclair.com/rss?1","title":"Jared Sinclair","updated":1554679244000,"velocity":0.0,"subscribers":1,"website":"https://jaredsinclair.com/","language":"en","description":"Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description."},{"feedId":"feed/http://www.takingnotes.co/atom.xml","id":"feed/http://www.takingnotes.co/atom.xml","title":"Doug Russell","updated":1541721600000,"velocity":0.0,"subscribers":1,"website":"http://takingnotes.co//","language":"en","state":"dormant"},{"feedId":"feed/http://www.red-sweater.com/blog/feed","id":"feed/http://www.red-sweater.com/blog/feed","title":"Red Sweater","updated":1568998271000,"velocity":0.5,"subscribers":1,"website":"https://red-sweater.com/blog","language":"en","description":"Official blog of Red Sweater Software"},{"feedId":"feed/http://mjtsai.com/blog/feed/","id":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","updated":1569609780000,"velocity":18.3,"subscribers":1,"website":"https://mjtsai.com/blog","language":"en"},{"feedId":"feed/http://jnack.com/blog/?feed=rss2","id":"feed/http://jnack.com/blog/?feed=rss2","title":"Nackblog","updated":1569781870000,"velocity":2.3,"subscribers":1,"website":"http://jnack.com/blog","language":"en","description":"Musings on photography, illustration, mobile apps, and more"},{"feedId":"feed/http://bitsplitting.org/feed/","id":"feed/http://bitsplitting.org/feed/","title":"Daniel Jalkut","updated":1563906209098,"velocity":0.1,"subscribers":1,"website":"https://bitsplitting.org","language":"en","description":"Chasing the impossible with Daniel Jalkut"},{"feedId":"feed/http://stmts.net/feed/","id":"feed/http://stmts.net/feed/","title":"Jesper","updated":1298233430000,"velocity":0.0,"subscribers":1,"website":"http://stmts.net","language":"en","state":"dormant","description":"On programming"},{"feedId":"feed/http://www.mikeash.com/pyblog/rss.py","id":"feed/http://www.mikeash.com/pyblog/rss.py","title":"NSBlog","updated":1530280470876,"velocity":0.0,"subscribers":2,"website":"http://www.mikeash.com/pyblog/","language":"en","state":"dormant","description":"Mac OS X and Cocoa programming"},{"feedId":"feed/http://corporationunknown.com/blog/feed/","id":"feed/http://corporationunknown.com/blog/feed/","title":"Corporation Unknown","updated":1420427842000,"velocity":0.0,"subscribers":1,"website":"http://corporationunknown.com/blog","language":"en","state":"dormant"},{"feedId":"feed/http://jamesdempsey.net/feed/","id":"feed/http://jamesdempsey.net/feed/","title":"James Dempsey","updated":1568304414000,"velocity":0.2,"subscribers":1,"website":"https://jamesdempsey.net","language":"en","description":"From Apple to Indie in three easy steps"},{"feedId":"feed/http://brian-webster.tumblr.com/rss","id":"feed/http://brian-webster.tumblr.com/rss","title":"Very Web. Such Blog. Wow.","updated":1530051926000,"velocity":0.0,"subscribers":1,"website":"https://brian-webster.tumblr.com/","language":"en","state":"dormant","description":"Brian Webster’s sporadic blogging about mostly programming stuff."},{"feedId":"feed/http://dangillmor.com/feed/","id":"feed/http://dangillmor.com/feed/","title":"Dan Gillmor","updated":1565895640000,"velocity":0.2,"subscribers":1,"website":"http://dangillmor.com","language":"en","description":"Just in case you were still wondering…"},{"feedId":"feed/http://www.jeffmcleman.com/blog/feed/","id":"feed/http://www.jeffmcleman.com/blog/feed/","title":"Jeff McLeman","updated":1563565996000,"velocity":0.1,"subscribers":1,"website":"https://www.jeffmcleman.com/blog","language":"en","description":"The Brooding Thoughts of an Untamed Mind"},{"feedId":"feed/http://www.caseyliss.com/rss","id":"feed/http://www.caseyliss.com/rss","title":"Liss is More","updated":1569830046093,"velocity":0.5,"subscribers":1,"website":"https://www.caseyliss.com","language":"en","description":"Posts to Liss is More"},{"feedId":"feed/http://ignorethecode.net/blog/rss/","id":"feed/http://ignorethecode.net/blog/rss/","title":"ignorethecode.net","updated":1532170394000,"velocity":0.0,"subscribers":1,"website":"http://ignorethecode.net","language":"en","state":"dormant","description":"Essays on usability, programming, and other nerd topics."},{"feedId":"feed/http://sheilasweblog.wordpress.com/feed/","id":"feed/http://sheilasweblog.wordpress.com/feed/","title":"Sheila's Weblog","updated":1237602766000,"velocity":0.0,"subscribers":1,"website":"https://sheilasweblog.wordpress.com","language":"en","state":"dormant","description":"Quilting, kitties, other fun stuff."},{"feedId":"feed/http://www.allenpike.com/feed/","id":"feed/http://www.allenpike.com/feed/","title":"Allen Pike","updated":1569829990142,"velocity":0.2,"subscribers":1,"website":"https://www.allenpike.com/","language":"en"},{"feedId":"feed/https://developer.apple.com/news/rss/news.rss","id":"feed/https://developer.apple.com/news/rss/news.rss","title":"iPhone Developer News","updated":1569357000000,"velocity":2.9,"subscribers":1,"website":"https://developer.apple.com/news/","language":"en","description":"Apple Developer News and Updates feed provided by Apple, Inc."},{"feedId":"feed/http://themainthread.com/feed.xml","id":"feed/http://themainthread.com/feed.xml","title":"The Main Thread","updated":1440820800000,"velocity":0.0,"subscribers":1,"website":"http://themainthread.com/","language":"en","state":"dormant"},{"feedId":"feed/http://www.gordonmeyer.com/atom.xml","id":"feed/http://www.gordonmeyer.com/atom.xml","title":"Gordon Meyer (posts on 'gordon meyer' (atom))","updated":1569066494000,"velocity":0.7,"subscribers":1,"website":"https://www.gordonmeyer.com/","language":"en"},{"feedId":"feed/http://www.mondaynote.com/feed/","id":"feed/http://www.mondaynote.com/feed/","title":"Monday Note","updated":1569786821000,"velocity":2.0,"subscribers":1,"website":"https://mondaynote.com?source=rss----c537d80ed0a---4","language":"en","description":"Media, Tech, Business Models viewed from Palo Alto and Paris - Medium"},{"feedId":"feed/http://www.randsinrepose.com/index.xml","id":"feed/http://www.randsinrepose.com/index.xml","title":"Rands In Repose","updated":1569830107339,"velocity":0.2,"subscribers":4,"website":"https://randsinrepose.com","language":"en"},{"feedId":"feed/http://furbo.org/feed/","id":"feed/http://furbo.org/feed/","title":"furbo.org","updated":1569830050733,"velocity":0.5,"subscribers":1,"website":"https://furbo.org","language":"en","description":"by Craig Hockenberry"},{"feedId":"feed/http://feeds.feedburner.com/NSHipster","id":"feed/http://feeds.feedburner.com/NSHipster","title":"NSHipster","updated":1569222000000,"velocity":0.7,"subscribers":1,"website":"https://nshipster.com/","language":"en","description":"NSHipster is a journal of the overlooked bits in Objective-C, Swift, and Cocoa. Updated weekly."},{"feedId":"feed/http://www.neglectedpotential.com/feed/","id":"feed/http://www.neglectedpotential.com/feed/","title":"Neglected Potential","updated":1537811051000,"velocity":0.0,"subscribers":1,"website":"http://www.neglectedpotential.com","language":"en","state":"dormant"}],"label":"Weblogs","created":1569829952574,"enterprise":false,"numFeeds":39,"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366"}] \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/gobalall_initial.json b/Frameworks/Account/AccountTests/Feedly/Initial/gobalall_initial.json new file mode 100644 index 000000000..a9af0f198 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/gobalall_initial.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.all","updated":1570012955549,"continuation":"16d88b62e1c:50f6b:18991ffa","items":[{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d8c10839d:1e1:fc4690a0","originId":"58087.pyqb40 at https://www.imore.com","recrawled":1570013612321,"updateCount":1,"fingerprint":"3c8fd5dc","content":{"content":"

\n

Twitter is not having a good morning

\n

What you need to know

\n
  • Twitter is experiencing API irregularity affecting all of its services.
  • \n
  • Reports of problems in-browser and on Tweetdeck.
  • \n
  • Also affecting Twitter for iOS.
  • \n

Twitter is experiencing irregularities with its APIs which is affecting all of its services this morning.

\n

We've been experiencing outages across Twitter and TweetDeck. You might have had trouble Tweeting, getting notifications, or viewing DMs. We're currently working on a fix, and should be back to normal soon.

— Twitter Support (@TwitterSupport) October 2, 2019
\n

The news first broke early this morning with outages affecting Twitter and Tweetdeck. Twitter and other users have reported experiencing problems trying to Tweet, getting notifcations and sending DMs.

\n

As per Twitter's API Status website"

\n
\n

Investigating - As of 2019-10-02 06:35:00 UTC, The Twitter data team is investigating a system irregularity causing lowered success rates across all APIs. We will provide an update as soon as we know more. Oct 2, 07:33 UTC

\n

Monitoring - As of 05:00:00, Oct 2 UTC - All APIs/products should be operating normally Oct 2, 05:14 UTC

\n

Update - The Twitter data team is investigating a system irregularity affecting all products/APIs that occurred starting on October 2 at approximately 00:50 UTC. Data may be delayed or missing at this time. We will provide an update as soon as we know more. Oct 2, 02:55 UTC

\n

Investigating - The Twitter data team is investigating a system irregularity affecting the streaming APIs that occurred starting on October 2 at approximately 00:50 UTC. Data may be delayed this time. We will provide an update as soon as we know more. Oct 2, 01:29 UTC

\n
\n

It seems the issue was resolved for a time, but has now resurfaced. As you can see from the screenshot below, Twitter for iOS is not currently letting users send Tweets from the app:

\n

\n

Below is a full list of Twitter's services and how they are affected.

\n

\"\"","direction":"ltr"},"title":"It's not just you: Twitter and TweekDeck are experiencing an outage","author":"Stephen Warwick","summary":{"content":"Twitter is not having a good morning\nWhat you need to know\nTwitter is experiencing API irregularity affecting all of its services.\nReports of problems in-browser and on Tweetdeck.\nAlso affecting Twitter for iOS.\nTwitter is experiencing irregularities with its APIs which is affecting all of its services this morning.\nWe've been experiencing outages across Twitter and TweetDeck. You might have had trouble Tweeting, getting notifications, or viewing DMs. We're currently working on a fix, and should be back to normal soon.— Twitter Support (@TwitterSupport) October 2, 2019\nThe news first broke early this morning with outages affecting Twitter and Tweetdeck. Twitter and other users have reported experiencing problems trying to Tweet, getting notifcations and sending DMs.\nAs per Twitter's API Status website"\nInvestigating - As of 2019-10-02 06:35:00 UTC, The Twitter data team is investigating a system irregularity causing lowered success rates across all APIs. We will provide...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/8ZVpHoI5j_0/twitter-api-irregularity-affecting-ios-tweetdeck","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/twitter-api-irregularity-affecting-ios-tweetdeck","type":"text/html"}],"crawled":1570012955549,"published":1570012843000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://b.vimeocdn.com/ts/442/609/442609877_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d8bf29449:1ce:fc4690a0","originId":"58085.pyqb40 at https://www.imore.com","fingerprint":"705bfa62","content":{"content":"

\n

Image via Riot

\n

This will only affect new installs, not current players

\n

What you need to know

\n
  • League of Legends may not work with macOS Catalina on release.
  • \n
  • Riot is unsure of compatibility due to broad release date.
  • \n
  • The issue will not affect users who have the game already installed, only new downloads.
  • \n

Riot games has announced that users may not be able to install League of Legends on macOS Catalina upon its release. Riot has said that due to the "broad release date" of Catalina they are unsure as to whether the game will be supported. In a post yesterday it said:

\n
\n

Due to the broad release date, we don't know if Catalina will support League of Legends and TFT on Mac devices. If you know a Mac user who might be interested in League of Legends or TFT, get them to install it as soon as possible to be safe!

\n

Existing players with League of Legends on their Macs will not have any problems or changes with League of Legends when Catalina rolls out. The game will patch normally and players will be able to play as they have been!

\n

At this time and with what we know, people will not be able to install League of Legends on their Mac devices on Catalina. So in the meantime, if you know someone who is interested in installing League of Legends, they should do so before Catalina debuts in October OR not update their macOS to Catalina until they do.

\n

We do not currently have an estimated timeline for when Catalina will be able to support League of Legends, but we will update players as we get a clearer look at its release date.

\n
\n

It's important to note that this could affect both League of Legends and spinoff Teamfight Tactics, which requires the LoL client in order to play. To recap, the issue will not affect any players who already have the game installed. So, if you are a LoL player with the game installed, there's nothing to worry about. If you're about to jump into the game for the first time on Mac, Riot strongly recommends that you should download the game before you install macOS Catalina (whenever that is released).

\n

As noted by Riot, it is unclear at this stage whether this is going to be an issue when Catalina is released. It is also unclear at this stage how long the problem might persist if indeed it does take effect. Most recent reports suggest Catalina could be released Friday, October 4, however Apple is yet to confirm a release date.

\"\"","direction":"ltr"},"title":"Catalina might not support League of Legends on release","author":"Stephen Warwick","summary":{"content":"Image via Riot\nThis will only affect new installs, not current players\nWhat you need to know\nLeague of Legends may not work with macOS Catalina on release.\nRiot is unsure of compatibility due to broad release date.\nThe issue will not affect users who have the game already installed, only new downloads.\nRiot games has announced that users may not be able to install League of Legends on macOS Catalina upon its release. Riot has said that due to the "broad release date" of Catalina they are unsure as to whether the game will be supported. In a post yesterday it said:\nDue to the broad release date, we don't know if Catalina will support League of Legends and TFT on Mac devices. If you know a Mac user who might be interested in League of Legends or TFT, get them to install it as soon as possible to be safe!\nExisting players with League of Legends on their Macs will not have any problems or changes with League of Legends when Catalina rolls out. The game will patch normally and...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/JWyLk-_b5BM/macos-catalina-may-not-support-league-legends-release","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/macos-catalina-may-not-support-league-legends-release","type":"text/html"}],"crawled":1570010993737,"published":1570010862000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d8ba1ae86:1b1:fc4690a0","originId":"58084.pyqb40 at https://www.imore.com","fingerprint":"b5f31d3c","content":{"content":"

\n

Image via Engadget

\n

Track down your conversations

\n

What you need to know

\n
  • Twitter has rolled out DM search in iOS.
  • \n
  • The feature was announced for testing in August.
  • \n
  • Search DMs by user names, informal names and message content.
  • \n

Twitter has rolled out a brand new feature for its iOS app, which means you can now search through your Direct Messages to find conversations. Twitter announced the move via... erm... Twitter yesterday.

\n

DM search is rolling out to everyone on iOS today. pic.twitter.com/nxbX19xjw7

— Nick Pacilio (@NickPacilio) October 1, 2019
\n

The service was previously announced in August and Twitter has been testing it on iOS since then. The new feature will allow users to search through their Direct Messages. Typing in any keyword will bring up results for Twitter user names, their informal names and conversation content much the same way searching through your iMessages does on iOS. This means you can now track down any direct message conversation you've had on Twitter by searching for the person you had it with, or something specific that you talked about.

\n

The news comes in the wake of another Twitter announcement confirming that its DM abuse filter has also been pushed to everyone.

\"\"","direction":"ltr"},"title":"You can now search your DMs on Twitter for iOS","author":"Stephen Warwick","summary":{"content":"Image via Engadget\nTrack down your conversations\nWhat you need to know\nTwitter has rolled out DM search in iOS.\nThe feature was announced for testing in August.\nSearch DMs by user names, informal names and message content.\nTwitter has rolled out a brand new feature for its iOS app, which means you can now search through your Direct Messages to find conversations. Twitter announced the move via... erm... Twitter yesterday.\nDM search is rolling out to everyone on iOS today. pic.twitter.com/nxbX19xjw7— Nick Pacilio (@NickPacilio) October 1, 2019\nThe service was previously announced in August and Twitter has been testing it on iOS since then. The new feature will allow users to search through their Direct Messages. Typing in any keyword will bring up results for Twitter user names, their informal names and conversation content much the same way searching through your iMessages does on iOS. This means you can now track down any direct message conversation you've had on Twitter b...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/8vdDDRESI8k/dm-search-rolls-out-twitter-ios","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/dm-search-rolls-out-twitter-ios","type":"text/html"}],"crawled":1570005692038,"published":1570005527000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=613222","fingerprint":"68a14535","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d8a2de26c:4a:5e4732b4","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

Twitter continues to expand the capabilities of its direct messaging functionality today. After rolling out its new abuse filter for Direct Messages yesterday, Twitter is now expanding the availability of DM search to all iOS users.

\n

more…

\n

The post Twitter starts rolling out Direct Message search to all iOS users appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/10/01/twitter-direct-message-search-ios/","type":"text/html"}],"crawled":1569981325932,"title":"Twitter starts rolling out Direct Message search to all iOS users","published":1569980556000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d8a041299:26:8e83c13e","originId":"58082.pyogg0 at https://www.imore.com","fingerprint":"64c3918d","content":{"content":"

\n

What you need to know

\n
  • Apple has updated Swift Playgrounds for iPadOS 13.
  • \n
  • New features include Dark Mode and SwiftUI.
  • \n
  • There is also new Learn to Code experiences.
  • \n

Swift Playgrounds now lives in the dark.

\n

Apple has updated its Swift Playgrounds app, an iPad exclusive, for iPadOS 13. Reported by 9to5Mac, the new changes in Swift 5.1 include Dark Mode, the ability to build with SwiftUI, and new Learn to Code lessons. The app's page on the App Store details all of the new updates:

\n
  • Use the new dark coding theme when running Dark Mode in iPadOS 13
  • \n
  • In Learn to Code, help Byte and friends explore their world at night when using Dark Mode in iPadOS 13\n• Build with the SwiftUI framework in new playgrounds you create\n• Includes Swift 5.1 and the iOS 13 SDK
  • \n

\n

If you've ever been interested in coding and own an iPad, Swift Playgrounds is a fantastic way to get started. The app is built by Apple and designed to teach you Swift code, Apple's own code language that continues to gain traction as the go-to language when coding apps for Apple's devices. In fact, use of swift code has reportedly doubled from iOS 12 to iOS 13. No matter how you are getting started, Apple has built Swift Playgrounds for you:

\n
\n

"Swift Playgrounds requires no coding knowledge, so it's perfect for students just starting out, from twelve to one-hundred-and-twelve. The whole time you are learning Swift, a powerful programming language created by Apple and used by professionals to build many of today's most popular apps. And because it's built to take full advantage of iPad and the real iOS SDK, Swift Playgrounds is a first-of-its-kind learning experience."

\n
\n

The new version of Swift Playgrounds is available today in the App Store.

\"\"","direction":"ltr"},"title":"Try coding with Dark Mode and SwiftUI on Swift Playgrounds for iPad","author":"Joe Wituschek","summary":{"content":"What you need to know\nApple has updated Swift Playgrounds for iPadOS 13.\nNew features include Dark Mode and SwiftUI.\nThere is also new Learn to Code experiences.\nSwift Playgrounds now lives in the dark.\nApple has updated its Swift Playgrounds app, an iPad exclusive, for iPadOS 13. Reported by 9to5Mac, the new changes in Swift 5.1 include Dark Mode, the ability to build with SwiftUI, and new Learn to Code lessons. The app's page on the App Store details all of the new updates:\nUse the new dark coding theme when running Dark Mode in iPadOS 13\nIn Learn to Code, help Byte and friends explore their world at night when using Dark Mode in iPadOS 13\n• Build with the SwiftUI framework in new playgrounds you create\n• Includes Swift 5.1 and the iOS 13 SDK\nIf you've ever been interested in coding and own an iPad, Swift Playgrounds is a fantastic way to get started. The app is built by Apple and designed to teach you Swift code, Apple's own code language that continues to gain trac...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/j09N6UbwY_4/code-you-own-dark-mode-and-swiftui-swift-playgorunds-ipad","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/code-you-own-dark-mode-and-swiftui-swift-playgorunds-ipad","type":"text/html"}],"crawled":1569978585753,"published":1569978519000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/06e5FJWgUfUSmDaPJIEZoGF1XOs=/0x68:2040x1136/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/10378819/DSCF3031.jpg","width":1200,"height":628,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"tag:daringfireball.net,2019:/linked//6.36096","fingerprint":"735777da","id":"ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16d89eeb096:2:5e4732b4","updated":1569975864000,"author":"John Gruber","alternate":[{"href":"https://blog.halide.cam/halide-1-14-time-to-get-switchy-a02f4c1b9ffc","type":"text/html"}],"crawled":1569977184406,"title":"Halide 1.14’s Lens Switcher and Field-of-View Guides","published":1569975863000,"origin":{"streamId":"feed/http://daringfireball.net/index.xml","htmlUrl":"https://daringfireball.net/","title":"Daring Fireball"},"content":{"direction":"ltr","content":"

Speaking of Halide, version 1.14 is out and has some sweet UI ideas for the 3-camera system on iPhone 11 Pro. Ben Sandofsky:

\n
\n

At a glance, our lens switcher looks the same as before; we kept\nit in the same spot so it doesn’t interfere with your viewfinder\nand is within easy reach. Keeping the viewfinder clear of any\nobstructions is one of our highest priorities.

\n

It works similarly, too, at first glance: just keep tapping to\ncycle between 1x, 2x, and 0.5x sizes.

\n

Unfortunately, switching cameras has a bit of a delay. If you’re\ncomposing a shot and want to compare the 0.5x and 1x lenses,\ncycling past that 2x lens feels slow and clunky. No sweat. Haptic\ntouch (or in common parlance, long press) the lens button to bring\nup our lens switcher.

\n
\n

This is a really clever bit of UI, very well-implemented. And part of that, as Sandofsky notes, is that it never obstructs the viewfinder.

\n"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"tag:daringfireball.net,2019://1.36095","recrawled":1569980785874,"updateCount":1,"fingerprint":"40aa6e5e","id":"ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16d89eeb096:1:5e4732b4","updated":1569979464000,"author":"John Gruber","summary":{"direction":"ltr","content":"You can shoot with 2× zoom with Night Mode, but when you do, it uses the wide angle camera and digitally, rather than optically, zooms to the 2× field of view."},"alternate":[{"href":"https://daringfireball.net/2019/10/night_mode_telephoto","type":"text/html"}],"crawled":1569977184406,"title":"★ Turns Out the Telephoto Camera on the iPhones 11 Does Not Support Night Mode","published":1569974551000,"origin":{"streamId":"feed/http://daringfireball.net/index.xml","htmlUrl":"https://daringfireball.net/","title":"Daring Fireball"},"content":{"direction":"ltr","content":"

From The Verge’s story on Deep Fusion, coming in iOS 13.2 beta 1 (which, I’ve been informed, is now scheduled to drop tomorrow or maybe even later this week, not today as originally planned):

\n
\n

With Deep Fusion, the iPhone 11 and 11 Pro cameras will have three\nmodes of operation that automatically kick in based on light\nlevels and the lens you’re using:

\n
    \n
  • The standard wide angle lens will use Apple’s enhanced Smart HDR\nfor bright to medium-light scenes, with Deep Fusion kicking in\nfor medium to low light, and Night mode coming on for dark\nscenes.

  • \n
  • The tele lens will mostly use Deep Fusion, with Smart HDR only\ntaking over for very bright scenes, and Night mode for very dark\nscenes.

  • \n
  • The ultrawide will always use Smart HDR, as it does not support\neither Deep Fusion or Night mode.

  • \n
\n
\n

Until yesterday, I was under the same impression as the above. But Sebastiaan de With — co-creator of the excellent iPhone camera app Halide — pointed out on Twitter that Night Mode only works with the regular wide-angle lens. You can shoot with 2× zoom with Night Mode, but when you do, it uses the wide angle camera and digitally, rather than optically, zooms to the 2× field of view.

\n

You can see this yourself in the EXIF data. Shoot an image using Night Mode at 2× zoom, and look at the lens information in Photos on the Mac. It will say “iPhone 11 Pro back triple camera 4.25mm f/1.8”. That’s the wide-angle camera. The telephoto camera is “6mm f/2”, and the ultra-wide is “1.54mm f/2.4”. (The front-facing camera is “2.71mm f/2.2”.)

\n

0.5× always uses the ultra-wide camera, because you can’t get that field of view otherwise. 1× always uses the wide angle, because that camera has the best sensor and fastest lens. But 2× doesn’t mean you’re always using the telephoto camera — in low light it will use the wide-angle camera and digital zoom. Previous iPhones with dual camera systems have done the same thing in low light conditions, but a lot of us — myself included — made the wrong assumption about Night Mode and “2× zoom”.

"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d89d099d9:5:c963e369","originId":"58078.pyogg0 at https://www.imore.com","fingerprint":"9ed290f0","content":{"content":"

Image: The Japanese House

\n

What you need to know

\n
  • The Japanese House released a new hit single "Something Has to Change."
  • \n
  • The single was highlighted by Apple Music on Twitter.
  • \n
  • It is the first single off the upcoming EP set to be released in November.
  • \n

Listen to "Something Has to Change" on Apple Music.

\n

Earlier this year, indie act The Japanese House released its first major album, Good At Feeling. But if you thought it would slow down the flow of new music, you were wrong. The Japanese House is back with a new hit single.

\n

In case you are unfamiliar with The Japanese House, it's not a band as the name suggests but a solo act Amber Bain. He hails from Great Britain and has been manning this project since 2015.

\n\n

"Something Has to Change" is the first new single off its upcoming new EP of the same name that will come out in November. Apple Music likes to spotlight artists on Twitter, and it did so with The Japanese House.

\n

The single is an upbeat anthem that is a great follow up to its last music that was more contemplative and atmospheric. It has a strong synth backdrop with catchy lyrics.

\n

If you haven't given The Japanese a shot, give "Something Has to Change" a listen on Apple Music. It might end up being your new favorite musical act.

\n

\n

Apple Music Subscription

\n

\n

Starting at $4.99 a month

\n

Apple Music is Apple's massive music service, comprising a subscription music catalog, iCloud Music Library syncing across your devices, Beats 1 live and algorithmic radio, customized playlists, and more artist exclusives than you can shake a stick at.

\n
\"\"","direction":"ltr"},"title":"Listen to The Japanese House’s new single on Apple Music","author":"Danny Zepeda","summary":{"content":"Image: The Japanese House\nWhat you need to know\nThe Japanese House released a new hit single "Something Has to Change."\nThe single was highlighted by Apple Music on Twitter.\nIt is the first single off the upcoming EP set to be released in November.\nListen to "Something Has to Change" on Apple Music.\nEarlier this year, indie act The Japanese House released its first major album, Good At Feeling. But if you thought it would slow down the flow of new music, you were wrong. The Japanese House is back with a new hit single.\nIn case you are unfamiliar with The Japanese House, it's not a band as the name suggests but a solo act Amber Bain. He hails from Great Britain and has been manning this project since 2015.\n"Something Has to Change" is the first new single off its upcoming new EP of the same name that will come out in November. Apple Music likes to spotlight artists on Twitter, and it did so with The Japanese House.\nThe single is an upbeat anthem that is a great follow up to its...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/oBzrJ5rpesw/listen-japanese-houses-new-single-apple-music","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/listen-japanese-houses-new-single-apple-music","type":"text/html"}],"crawled":1569975212505,"published":1569974901000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"tag:daringfireball.net,2019://1.36095","fingerprint":"2b0aa117","id":"SfSSQbsJpAnFmSl8cDkL3bQGKFOfsp48QsvCmTUjxYM=_16d89cb360c:51588:18991ffa","updated":1569974553000,"author":"John Gruber","summary":{"direction":"ltr","content":"You can shoot with 2× zoom with Night Mode, but when you do, it uses the wide angle camera and digitally, rather than optically, zooms to the 2× field of view."},"alternate":[{"href":"https://daringfireball.net/2019/10/night_mode_telephoto","type":"text/html"}],"crawled":1569974859276,"title":"★ Turns Out the Telephoto Camera on the iPhones 11 Does Not Support Night Mode","published":1569974551000,"origin":{"streamId":"feed/http://daringfireball.net/feeds/main","htmlUrl":"https://daringfireball.net/","title":"Daring Fireball"},"content":{"direction":"ltr","content":"

From The Verge’s story on Deep Fusion, coming in iOS 13.2 beta 1 (which, I’ve been informed, is now scheduled to drop tomorrow, not today as originally planned):

\n
\n

With Deep Fusion, the iPhone 11 and 11 Pro cameras will have three\nmodes of operation that automatically kick in based on light\nlevels and the lens you’re using:

\n
    \n
  • The standard wide angle lens will use Apple’s enhanced Smart HDR\nfor bright to medium-light scenes, with Deep Fusion kicking in\nfor medium to low light, and Night mode coming on for dark\nscenes.

  • \n
  • The tele lens will mostly use Deep Fusion, with Smart HDR only\ntaking over for very bright scenes, and Night mode for very dark\nscenes.

  • \n
  • The ultrawide will always use Smart HDR, as it does not support\neither Deep Fusion or Night mode.

  • \n
\n
\n

Until yesterday, I was under the same impression as the above. But Sebastiaan de With — co-creator of the excellent iPhone camera app Halide — pointed out on Twitter that Dark Mode only works with the regular wide-angle lens. You can shoot with 2× zoom with Night Mode, but when you do, it uses the wide angle camera and digitally, rather than optically, zooms to the 2× field of view.

\n

You can see this yourself in the EXIF data. Shoot an image using Night Mode at 2× zoom, and look at the lens information in Photos on the Mac. It will say “iPhone 11 Pro back triple camera 4.25mm f/1.8”. That’s the wide-angle camera. The telephoto camera is “6mm f/2”, and the ultra-wide is “1.54mm f/2.4”. (The front-facing camera is “2.71mm f/2.2”.)

\n

0.5× always uses the ultra-wide camera, because you can’t get that field of view otherwise. 1× always uses the wide angle, because that camera has the best sensor and fastest lens. But 2× doesn’t mean you’re always using the telephoto camera — in low light it will use the wide-angle camera and digital zoom. Previous iPhones with dual camera systems have done the same thing in low light conditions, but a lot of us — myself included — made the wrong assumption about Night Mode and “2× zoom”.

"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/m3ztxUEeETYXD9gGfyEjUpS0GFE=/0x0:800x533/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59662701/800x_1.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=613215","fingerprint":"64179143","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d89bff80b:51566:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

Apple has updated yet another one of its applications with support for new iOS 13 and iPadOS features. Swift Playgrounds has been updated today with Dark Mode support, Learn to Code enhancements, and more.

\n

more…

\n

The post Swift Playgrounds for iPadOS updated with Dark Mode, new SwiftUI support, more appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/10/01/swift-playground-ipad-dark-mode/","type":"text/html"}],"crawled":1569974122507,"title":"Swift Playgrounds for iPadOS updated with Dark Mode, new SwiftUI support, more","published":1569973482000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=613187","fingerprint":"56964af4","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d89bff80b:51565:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

Even though September was a busy month for Apple, the year isn’t over just yet. Now that we’re officially in October, expect a new round of hardware and software releases from Apple. Ranging from the oft-rumored Apple Tag to the release of macOS Catalina, there’s a lot to look forward to — and it could all culminate in an October Apple event. more…

\n

The post Apple in October expectations: Apple Tag, 16-inch MacBook Pro, new iPad Pro, more appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/10/01/apple-october-event-expectations/","type":"text/html"}],"crawled":1569974122507,"title":"Apple in October expectations: Apple Tag, 16-inch MacBook Pro, new iPad Pro, more","published":1569971159000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"originId":"https://inessential.com/2019/10/01/on_bullying_in_our_community","fingerprint":"446362f9","id":"+jHfsXnBCVfCstSIW1WDumAyigT4rnsUPnI5WFxgnAU=_16d89942421:5148f:18991ffa","summary":{"direction":"ltr","content":"

Janie Larson writes about being bullied, and you should read it.

\n

It’s natural to wonder who the bully is and who the conference organizers are — but I’m resisting the temptation to spend any time on it. It’s not a puzzle to be solved. Janie’s explicit that she doesn’t want this to result in anyone getting harassed, and she doesn’t want to start a feud. Respect that.

\n

Instead, she talks about the human cost of being bullied, and she presents a guide for handling bullying — which is written especially for people witnessing it.

\n

Even if you think it’s unlikely that you yourself will ever be bullied (and you might not think that), it’s worth remembering that you might see it happen to someone else. I hope you and I would do the right thing.

"},"alternate":[{"href":"https://inessential.com/2019/10/01/on_bullying_in_our_community","type":"text/html"}],"crawled":1569971250209,"title":"On Bullying in Our Community","published":1569969481000,"origin":{"streamId":"feed/http://ranchero.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/10/01/on_bullying_in_our_community","fingerprint":"446362f9","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d8993d56d:5148e:18991ffa","summary":{"direction":"ltr","content":"

Janie Larson writes about being bullied, and you should read it.

\n

It’s natural to wonder who the bully is and who the conference organizers are — but I’m resisting the temptation to spend any time on it. It’s not a puzzle to be solved. Janie’s explicit that she doesn’t want this to result in anyone getting harassed, and she doesn’t want to start a feud. Respect that.

\n

Instead, she talks about the human cost of being bullied, and she presents a guide for handling bullying — which is written especially for people witnessing it.

\n

Even if you think it’s unlikely that you yourself will ever be bullied (and you might not think that), it’s worth remembering that you might see it happen to someone else. I hope you and I would do the right thing.

"},"alternate":[{"href":"https://inessential.com/2019/10/01/on_bullying_in_our_community","type":"text/html"}],"crawled":1569971230061,"title":"On Bullying in Our Community","published":1569969481000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"visual":{"url":"http://cdn3.sbnation.com/entry_photo_images/9192995/Untitled_large.jpg","width":630,"height":420,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=613188","fingerprint":"e3f74796","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d8988fd3e:51455:18991ffa","author":"Filipe Espósito","summary":{"direction":"ltr","content":"
\n

Following the release of iOS 13 and iPadOS for all users, several developers have already updated their applications with compatibility with the new features. PDF Expert 7 from Readdle, one of the most popular PDF managers for iPhone and iPad, has been updated today with multiple windows on iPad, Dark Mode, enhanced markup features, and more. That is the first significant update to the app since the launch of its seventh version.

\n

more…

\n

The post PDF Expert 7 updated with multiple windows support and more for iPadOS 13 appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/10/01/pdf-expert-ipados-13/","type":"text/html"}],"crawled":1569970519358,"title":"PDF Expert 7 updated with multiple windows support and more for iPadOS 13","published":1569968795000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d89797893:1f1:db1c1742","originId":"58076.pyogg0 at https://www.imore.com","fingerprint":"e6aef3dc","content":{"content":"

\n

What you need to know

\n
  • The Temper Trap's breakthrough album Conditions debuted ten years ago.
  • \n
  • The album produced the super catchy hit "Sweet Disposition."
  • \n
  • Songs from the album were feature in 500 Days of Summer and FIFA 10.
  • \n

It spawned the hit "Sweet Disposition."

\n

Back in 2009, if you were paying any kind of attention to music, then you probably at some point heard The Temper Trap's "Sweet Disposition." The mega hit song was one of the transformative songs that hit the airwaves that year. It was off the band's transformative debut album Conditions that launched their career into a whole new level.

\n

It's been ten years since the release of Conditions, so let's look back at the album's legacy.

\n

Before their big break, The Temper Trap was formed in Australia where they roamed around for a few years in anonymity with only EPs to their name before they recorded their debut album. That album was Conditions, which they recorded from 2008 to 2009.

\n\n

The first single off the album was "Sweet Disposition" and it put the band on the map. The super catchy song utilized a strong rift, lively drums and soaring vocals by frontman Dougy Mandagi to create a melodic rock anthem that just caught on. One thing that truly helped the song was being feature in the cult hit 500 Days of Summer that has since become one of the most iconic romantic comedys of all time.

\n

The other major hit from the album was "Science of Fear" which gained a large followed after being featured in the FIFA 10 soundtrack.

\n

The Temper Trap continues to make music to this day, but whenever you search them nowadays, the first song that shows up is "Sweet Disposition." It's also constantly featured in best hits of the 2000's playlists on music streaming service and rightfully so. No great playlists from the this era is complete without The Temper Trap.

\n

You can listen to the entire album on Apple Music.

\n

\n

Apple Music Subscription

\n

\n

Starting at $4.99 a month

\n

Apple Music is Apple's massive music service, comprising a subscription music catalog, iCloud Music Library syncing across your devices, Beats 1 live and algorithmic radio, customized playlists, and more artist exclusives than you can shake a stick at.

\n
\"\"","direction":"ltr"},"title":"The Temper Trap’s breakthrough album Conditions turns ten","author":"Danny Zepeda","summary":{"content":"What you need to know\nThe Temper Trap's breakthrough album Conditions debuted ten years ago.\nThe album produced the super catchy hit "Sweet Disposition."\nSongs from the album were feature in 500 Days of Summer and FIFA 10.\nIt spawned the hit "Sweet Disposition."\nBack in 2009, if you were paying any kind of attention to music, then you probably at some point heard The Temper Trap's "Sweet Disposition." The mega hit song was one of the transformative songs that hit the airwaves that year. It was off the band's transformative debut album Conditions that launched their career into a whole new level.\nIt's been ten years since the release of Conditions, so let's look back at the album's legacy.\nBefore their big break, The Temper Trap was formed in Australia where they roamed around for a few years in anonymity with only EPs to their name before they recorded their debut album. That album was Conditions, which they recorded from 2008 to 2009.\nThe first single off the album was "Sweet...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/ls30KUGVduU/temper-traps-breakthrough-album-conditions-turns-ten-listen-it-apple-music","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/temper-traps-breakthrough-album-conditions-turns-ten-listen-it-apple-music","type":"text/html"}],"crawled":1569969502355,"published":1569969398000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/qEtBNTmNKQvUro7McqHPXL-2OOM=/0x0:6720x4480/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59664621/marshall_majorIII_aniconinthemaking_product_rgb_highres_2.0.jpg","width":1310,"height":878,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d896af822:1ac:db1c1742","originId":"58075.pyogg0 at https://www.imore.com","fingerprint":"13f55011","content":{"content":"

This is the band's first new studio album in seven years.

\n

\n

What you need to know

\n
  • Chromatics have released a surprise album called Closer to Grey.
  • \n
  • It's the band's seventh studio album and follows 2012's Kill for Love.
  • \n
  • You can listen to Closer to Grey now on Apple Music.
  • \n

Fans of Chromatics are being treated to a nice present tonight: The band is releasing a new studio album called Closer to Grey, their first in seven years. It follows 2012's lauded Kill for Love.

\n

According to Pitchfork, Closer to Grey comes in addition to Dear Tommy, another album Chromatics announced in 2014 but hasn't been released.

\n

Closer to Grey tracklist:

\n
\n

01 The Sound of Silence
\n02 You're No Good
\n03 Closer to Grey
\n04 Twist the Knife
\n05 Light as a Feather
\n06 Move a Mountain
\n07 Touch Red
\n08 Through the Looking Glass
\n09 Whispers in the Hall
\n10 On the Wall
\n11 Love Theme From Closer to Grey
\n12 Wishing Well

\n
\n

At the time, the band said Dear Tommy would be out in 2015, but a full album never came out. Instead, the band released multiple singles between February 2015 and May 2018.

\n

Closer to Grey is apparently being referred to as the band's seventh studio album, which means Dear Tommy could still be number six. Kill for Love was the band's fifth record.

\n

In any case, Closer to Grey is slated to be available on Apple Music at midnight Eastern on Wednesday, October 2.

\n

Sweet Music

\n

Apple Music

\n

\n

From $4.99 at Apple

\n
\"\"","direction":"ltr"},"title":"Chromatics releasing new album Closer to Grey on Apple Music tonight","author":"Brandon Russell","summary":{"content":"This is the band's first new studio album in seven years.\nWhat you need to know\nChromatics have released a surprise album called Closer to Grey.\nIt's the band's seventh studio album and follows 2012's Kill for Love.\nYou can listen to Closer to Grey now on Apple Music.\nFans of Chromatics are being treated to a nice present tonight: The band is releasing a new studio album called Closer to Grey, their first in seven years. It follows 2012's lauded Kill for Love.\nAccording to Pitchfork, Closer to Grey comes in addition to Dear Tommy, another album Chromatics announced in 2014 but hasn't been released.\nCloser to Grey tracklist:\n01 The Sound of Silence 02 You're No Good\n03 Closer to Grey\n04 Twist the Knife\n05 Light as a Feather\n06 Move a Mountain\n07 Touch Red\n08 Through the Looking Glass\n09 Whispers in the Hall\n10 On the Wall\n11 Love Theme From Closer to Grey\n12 Wishing Well\nAt the time, the band said Dear Tommy would be out in 2015, but a full album never...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/UW18lVFhG88/chromatics-releasing-new-album-closer-grey-apple-music-tonight","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/chromatics-releasing-new-album-closer-grey-apple-music-tonight","type":"text/html"}],"crawled":1569968551970,"published":1569968304000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://b.vimeocdn.com/ts/452/218/452218069_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"keywords":["Career","Conferences"],"originId":"http://redqueencoder.com/?p=1588","fingerprint":"2ac32717","id":"4rURtf64IHW6ygZs9tzPieSmabigMiZqHVuUPwvicWg=_16d895c218f:51370:18991ffa","author":"admin","summary":{"direction":"ltr","content":"I have a bully. Not a sexual harasser or a rapist or a men’s rights activist. A good old fashioned playground bully. I have mostly kept this story to myself for over a year, but I don’t feel I can continue to do so. It is destroying my peace of mind and making me feel […]"},"alternate":[{"href":"http://redqueencoder.com/bullying/","type":"text/html"}],"crawled":1569967579535,"title":"Bullying","published":1569964563000,"origin":{"streamId":"feed/http://redqueencoder.com/feed/","htmlUrl":"http://redqueencoder.com","title":"The Red Queen Coder"},"content":{"direction":"ltr","content":"

I have a bully.

\n

Not a sexual harasser or a rapist or a men’s rights activist. A good old fashioned playground bully.

\n

I have mostly kept this story to myself for over a year, but I don’t feel I can continue to do so. It is destroying my peace of mind and making me feel terrible. I tried to ignore my feelings about this, hoping they would go away, but they have not. I need to share my story in order to move on from this.

\n

I’m not posting this because I want anyone to do anything about this specific incident. I just want people to listen and to understand.

\n

The Beginning

\n

My first interaction with my bully was back in 2015. 2015 was the first year I really got into conference speaking. I had finished up school the year before and conference speaking was the only way I had to network and get my name out there in order to find a job.

\n

I was invited to speak at a conference in 2015 by a friend of mine. As many of you know, I used to be in an abusive marriage. My previous husband would not allow me to have money to buy textbooks while I was going to school for computer programming. My friend sent me books, watched my first shitty conference talk while it was being live streamed, and used his connections to try to help me find a job.

\n

My friend did this when I was nobody. I wasn’t an author yet. I didn’t have a blog. No one knew who I was. He helped me because it was a kind thing to do and he had no expectation that I would ever become anything. To me, the way someone treats you when they don’t get any benefit from it means a lot. I see way too many people who only network with people who are more visible than they are and tell anyone just starting out to fuck off. I don’t like those people.

\n

I was invited to speak at a conference my friend was organizing, but I could not make it because I found out my ex-husband had put us significantly in debt. I felt very sad about not being able to go.

\n

My friend also invited my bully to the conference. My bully could not go for different reasons than I had. They were very vocal about how unhappy they were with my friend regarding his handling of his conference. They started a Twitter mob against my friend and started a whisper network around him.

\n

I felt some loyalty to my friend after his working to help me before I had a career. I kept hearing people at conferences say how terrible it was that he was being attacked, but no one said anything. I was probably dumb, but I put my neck out to defend my friend because no one else would. I understood that my bully would be unhappy about my defense and I really did the best I could to say this whole thing was probably a misunderstanding, as is wont to happen when people communicate online.

\n

After I posted that post, the talk stopped. I thought I made a difference and got people to leave him alone. I now realize it’s probable that people were still talking about him, but that I had shown myself to be untrustworthy and thus no one included me in these conversations.

\n

I knew there would probably be repercussions for my actions, but I figured I did the right thing and I would feel morally okay with whatever the blowback was.

\n

2018

\n

Four years passed and I didn’t feel the blowback. I noticed I was never invited to a conference my bully was speaking at, but that was fine. I didn’t really care to interact with them.

\n

My luck ran out in 2018.

\n

A talk of mine was accepted at a conference. I had to spend nine months working on it. It was incredibly labor intensive, but I was happy to have the opportunity to speak.

\n

Three weeks before the conference, I saw that the conference organizers announced my bully would be a keynote speaker at the conference.

\n

I wrestled with what to do. I hadn’t had any contact with my bully in four years. I half hoped my bully had forgotten the whole incident and found someone else to be mad at about something. I thought about approaching the conference organizers about the situation, but I thought that would be petty. My bully hadn’t done anything so what would I complain about? What could they even do about it?

\n

I found out.

\n

Six days before the conference I was contacted on the conference Slack by the organizers telling me that they had to talk to me and had set a meeting for 10:00 the following morning. I kind of joked that I hoped I hadn’t done something wrong. They repeated that I was required to speak to them tomorrow morning at 10:00. They would not elaborate what had happened or what we would be speaking about. I had a sinking feeling this was about my bully. I was right.

\n

The organizers, looking like police interrogators, told me there had been a complaint about me. They said they did not want to hear my side of the story because they didn’t care and would not believe me. They told me they had stringent conditions I was required to follow if I was to be allowed to keep my slot at their conference:

\n
  • I was not to be within 500 feet of my bully
  • I was not to be in the same room as my bully
  • I was not to speak to my bully
  • I was not to speak about my bully to anyone at the conference
  • I was not to attend a networking event my bully wanted to attend
  • I was not to tell anyone that this conversation happened
\n

I was dumbstruck. I was not surprised my bully had complained about me. I was surprised at how I was being treated by the organizers. I considered them to be friends. I had had them to my house and they had slept under my roof. They met my parents and I had fed them food I made myself. I considered this to be a deep transgression of my hospitality and relationship with them.

\n

My initial inclination was to tell them to fuck off and that I would not attend their conference. I felt that this would have looked bad professionally. I spent nine months working on my talk and it could not be replaced easily. I was also bringing my now-husband with me. He had bought a conference and a plane ticket. I didn’t feel I could tell him that he wasted that money because I was being petty.

\n

So I swallowed my pride and faked my way through the conference. I spent most of the conference hiding in my room with my husband. I came out for my talks and for meals. I briefly saw my bully as I was taking my dog outside to walk her and I was terrified that they would complain about me violating the terms of my parole and have me kicked out before I could give my talk.

\n

The biggest thing that bothers me about this situation is that I had reported harassment two years earlier, the first time I attended their conference. Someone at the conference touched me inappropriately and tried to get me to take them back to my hotel room to have sex. I reported the incident and they didn’t believe me. They told me it was a misunderstanding, the perpetrator was European, and that I had misunderstood his intentions. I had to physically demonstrate on one of the organizers how I had been touched for them to take it seriously.

\n

So like sexual assault is just fine, but just make sure you don’t defend someone on Twitter? I wasn’t allowed to tell anyone about that incident either. My bad.

\n

Aftermath

\n

I tried to tell myself that none of this is important and that I didn’t care. I felt deeply foolish for thinking these people were my friends when they clearly didn’t feel that way about me. I felt like I had been conned and was too stupid to realize it until it was too late. I was angry about the number of hours I wasted working on their projects and I wanted that time back.

\n

I broke my agreement to not talk about this incident a few times with a few trusted friends in the community. Any time I tried to obscure the name of my bully, they always immediately knew who it was. No one I told was surprised any of this happened. They were always deeply sympathetic and told me that everyone knows how this person is.

\n

Imagine my shock to see my confidants going out of their way to socialize and interact with my bully. I would see them having long involved conversations on Twitter or being asked to collaborate on their side projects.

\n

I called one of them out about this. I reminded him about how I had been treated. His response was, “Yeah, but that has nothing to do with me. I can be friend with both of you and be Jonny in the Middle because I’m basically Switzerland.”

\n

At first I tried not to let this bother me. I didn’t want to tell my friends they had to choose between me and a bully because that seemed shrewish. Also mostly because I know that my side would not be chosen. But the longer this goes on, the more this bothers me.

\n

All of these people know this person is a bully. They know this person’s behavior is terrible. Conference organizers know that this person will ruin them on Twitter if they don’t do everything the bully says. So why do they keep giving my bully a platform? Why do they keep inviting the bully to speak and give them power and visibility? Why do they knowingly stick their neck in a noose and then punish anyone who threatens a situation where it might cut off their air?

\n

I kept thinking that one day someone would catch wise to this situation and they would stop inviting my bully to speak, but it hasn’t happened. I realized that it’s not because people don’t know this person is a bully. It’s because people don’t care. They don’t care that this person treats people badly because they get something out of interacting with them.

\n

That’s the piece that clicked into place that really made me feel I needed to talk about this. I knew my bully had mental health issues. Not that it’s an excuse for behavior. I don’t blame them for complaining about me to the conference organizers. I expected them to. I would not be surprised if my bully used their clout to keep me away from any conference they were at for so many years.

\n

The people I do blame are the ones who know this is happening and do nothing.

\n

I don’t know how I can continue to be friends with people who knowingly associate themselves with an abusive bully. I don’t know how they can feel that it’s okay for someone to ruin people’s lives as long as it’s not theirs.

\n

I also keep thinking about how this was how I was treated when I WAS A SPEAKER!! I keep thinking of all the people this bully might have damaged who didn’t have the clout/visibility I do. What if I had been an attendee? A student? How many people have had their careers damaged or left the community because they ran afoul of this person? We’ll never know.

\n

What Do I Want Out of This?

\n

Here is what I DON’T want out of this:

\n
  • I don’t want anyone who figured out who these people are to harass them on Twitter in any capacity. That’s their MO, not mine.
  • I don’t want to start a feud. This is about me talking about my feelings, not trying to start some kind of retribution against anyone. I’m not trying to damage anyone’s business/living because I feel slighted. I just want to be able to speak openly about how this made me feel.
  • I don’t want an apology. The organizers made a calculated risk that they could treat me like shit and have nothing bad happen to them because I have no power to make their lives miserable and they were right. That burns me up a little, but it’s reality. I don’t want a fake apology from anyone trying to make themselves look better. The bridge is burned and it’s not coming back.
\n

I do want two things out of this. The first thing is I just want to talk about how this made me feel. I feel deeply angry and hurt by this situation. I have bottled this up for over a year and I am tired of it. I kept telling myself I had no right to feel upset about this because there are kids locked in concentration camps along the border and the world is slowly microwaving to death. Being slighted at a conference is the most First World Problem there is. Also, I may be in the wrong here. What if I did something terrible and I was told not to tell because it would look bad for me? I know I should get over this and if I just keep trying to be a good person and ship projects that in the long run this doesn’t matter. But I feel I have the right to my anger and my pain regarding this.

\n

I want to be clear that I didn’t expect the organizers to tell my bully to fuck off. I was pretty sure my bully would complain about me, but I wish the organizers had handled their interaction with me differently. Instead of treating me like a pedophile caught next to a playground, I wish they had privately reached out and been like, “Hey, look. Someone complained about you. We know you’re cool. Do you mind just like avoiding them so that they don’t cause an incident?” I would have been like, sure, no problem. See you next week. They chose instead to completely burn our entire personal and professional relationship. It wasn’t necessary. They chose to do it. That kind of stings.

\n

I want to get over this. This is a festering wound that never heals and constantly reopens over nothing. I don’t trust people who were my friends. I don’t feel comfortable or trust anyone anymore. I am consumed with a desire to become so powerful that no one can ever fuck me over like this again. I hate feeling this way. I want to build things that make me happy. I want this to not bother me anymore.

\n

The second thing I want out of this is for people to realize there is no neutral position in a situation where someone is being bullied or harassed. If someone is being bullied because they disagreed with another person’s behavior towards someone else and they are being harassed, you don’t get to sit there and think, “Sucks to be them! Should have keep their mouth shut!” and still be a good person.

\n

There is a somewhat large subset of people on Twitter who feel that any kind of disagreement with them constitutes harassment. It does not. Hating someone isn’t justification for complaining about their presence at a professional event.

\n

How to Handle Bullying

\n

Here’s a handy guide to dealing with bullying.

\n

If you see someone being harassed, you gently insert yourself into the conversation and you tell the harasser that their behavior is inappropriate. The harasser must back off and apologize and the incident is over. There is no retaliation or blame from either party. It’s just over.

\n

If you see someone try to step between a harasser and someone else who is also being harassed, you don’t sit back smugly and think they should have kept to themselves. You step in and also assert that this behavior isn’t acceptable.

\n

This works even in the situations where people are afraid of being called out as creepy or socially awkward. If you are bothering someone and you don’t mean to, the person just wants the behavior to stop. Someone lets you know that you are bothering another person, you apologize, and you leave them alone. Kindergartners understand this. Had I been asked to apologize to my bully but be allowed free range of the conference, I would have been happy to do so. But the point was retribution not fear.

\n

Bullies are allowed to act the way they do because most people sit back and let it happen because they figure it’s nothing to do with them. It is to do with you. If you sit back and let people behave this way, you’re contributing to a hostile environment. Your friends see when you sit back and let them be treated like crap. They remember. They pretend it doesn’t hurt them when you tell them later that what happened sucked but you didn’t want to get involved, but it does. You are hurting your friends when you let them be harassed.

\n

We can’t do anything about the concentration camps along the border or the inevitable heat death of the Universe, but god damn it, we can make our community a little bit more welcoming and friendly place for everyone.

"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=613177","fingerprint":"75e978ac","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d89520343:5132b:18991ffa","author":"Michael Potuck","summary":{"direction":"ltr","content":"
\n

Want to master instantly switching from shooting photos to capturing a video? Follow along for how to use the QuickTake video shortcut with the iPhone 11 and iPhone 11 Pro cameras.

\n

more…

\n

The post How to use the QuickTake video shortcut with iPhone 11 and 11 Pro cameras appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/10/01/use-quicktake-video-shortcut-iphone-11-pro/","type":"text/html"}],"crawled":1569966916419,"title":"How to use the QuickTake video shortcut with iPhone 11 and 11 Pro cameras","published":1569965136000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d894cd2ff:108:db1c1742","originId":"58073.pyogg0 at https://www.imore.com","fingerprint":"a4699499","content":{"content":"
\n\n

What you need to know

\n
  • PDF Expert has been updated with iOS and iPadOS 13 support.
  • \n
  • That means all of the new hotness is included.
  • \n
  • Dark Mode, multi-window, and PencilKit support are all here.
  • \n

Editing PDFs has never been so fun or looked so great.

\n

Popular PDF editing app PDF Expert has been updated to add support for all of the new hotness that iOS and iPadOS 13 offer up. The update is free and available for download now.

\n
\n

After the public release of PDF Expert 7 back in August, our team has been busy working on readying the next set of major features to be shipped in the app. PDF Expert 7 is the biggest release of PDF Expert to date, bringing in a ton of features and improvements to your PDF experience on your iPhone and iPad. Today, we're delighted to introduce the new PDF Expert with some exciting new features and powerful experiences made possible by iOS 13 and iPadOS for iPhones and iPads.

\n
\n

Like so many of the other apps that have been updated in recent days, PDF Expert now supports Dark Mode. Not only does that look great, but the theory goes that it will also be less harsh on your eyes. If you're a secret midnight PDF editor, this update is for you.

\n

Developer Readdle has also included support for PencilKit, improving the way Apple Pencil owners will interact with documents on-screen. Speaking of interacting on-screen, new gestures are also included to make it easier than ever to work with your PDF of choice.

\n

For the true productivity fiend Readdle has also added support for multiple windows. This is multitasking at its best with two different documents open side-by-side. Comparing versions of files or just taking notes while editing a PDF are just two of the situations where having multiple PDF Expert windows open at a single time can be super helpful. But hardcore multitaskers won't need us to tell them that!

\"\"","direction":"ltr"},"title":"PDF Expert updated with Dark Mode, PencilKit support, and more","author":"Oliver Haslam","summary":{"content":"What you need to know\nPDF Expert has been updated with iOS and iPadOS 13 support.\nThat means all of the new hotness is included.\nDark Mode, multi-window, and PencilKit support are all here.\nEditing PDFs has never been so fun or looked so great.\nPopular PDF editing app PDF Expert has been updated to add support for all of the new hotness that iOS and iPadOS 13 offer up. The update is free and available for download now.\nAfter the public release of PDF Expert 7 back in August, our team has been busy working on readying the next set of major features to be shipped in the app. PDF Expert 7 is the biggest release of PDF Expert to date, bringing in a ton of features and improvements to your PDF experience on your iPhone and iPad. Today, we're delighted to introduce the new PDF Expert with some exciting new features and powerful experiences made possible by iOS 13 and iPadOS for iPhones and iPads.\nLike so many of the other apps that have been updated in recent days, PDF Expert now...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/NwQv9ZObwRE/pdf-expert-updated-dark-mode-pencilkit-support-and-more","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/pdf-expert-updated-dark-mode-pencilkit-support-and-more","type":"text/html"}],"crawled":1569966576383,"published":1569966557000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://cdn1.sbnation.com/entry_photo_images/9188405/LG_G_Flex-3_large_verge_medium_landscape.jpg","width":640,"height":426,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d89471ca5:105:db1c1742","originId":"58071.pyogg0 at https://www.imore.com","fingerprint":"c993b0f3","content":{"content":"

The Apple CEO is currently traveling around Europe.

\n

\n

What you need to know

\n
  • Apple CEO Tim Cook sat down for an interview with German publication Stern
  • \n
  • Cook talked about the iPhone 11, Apple TV, and more.
  • \n
  • Cook is currently on a tour of Europe.
  • \n

It's been a busy few days for Apple CEO Tim Cook, who has met with developers, visited retail stores, and enjoyed oversized beers during his trip to Europe. In the midst of all that, Cook sat down for an interview with German publication Stern, where he discussed the iPhone 11, Apple TV+, and accusations of anti-competitive behavior.

\n

When asked about the iPhone 11's price, Cook said Apple always tries to price its products as low as possible.

\n

"We always try to keep our prices as low as possible, and fortunately we were able to lower the price of the iPhone this year," Cook said.

\n

The iPhone 11's price was actually one of the bigger surprises of Apple's September event. Unlike the iPhone XR, which retailed for $749, the iPhone 11 starts at $699, even though it features a more advanced dual-camera system, A13 Bionic chip, and massive battery.

\n

Cook was also asked about Apple TV+ and how he sees it competing against services such as Netflix.

\n

"I do not think the competition is afraid of us, the video sector works differently: It's not about whether Netflix wins and we lose, or if we win and they lose. Many people use multiple services, and we are now trying to become one of them," Cook said.

\n

Finally, Cook briefly talked about the numerous antitrust lawsuits aimed Apple's way.

\n

"No reasonable person would ever call Apple a monopoly," Cook said, arguing that Apple faces strong competition in every market it's active.

\n

On his latest stop, Cook was at Apple Champs-Élysées, where he said he dropped in on a Today at Apple photography session.

\"\"","direction":"ltr"},"title":"Tim Cook interview covers iPhone 11 price, Apple TV+, and more","author":"Brandon Russell","summary":{"content":"The Apple CEO is currently traveling around Europe.\nWhat you need to know\nApple CEO Tim Cook sat down for an interview with German publication Stern\nCook talked about the iPhone 11, Apple TV, and more.\nCook is currently on a tour of Europe.\nIt's been a busy few days for Apple CEO Tim Cook, who has met with developers, visited retail stores, and enjoyed oversized beers during his trip to Europe. In the midst of all that, Cook sat down for an interview with German publication Stern, where he discussed the iPhone 11, Apple TV+, and accusations of anti-competitive behavior.\nWhen asked about the iPhone 11's price, Cook said Apple always tries to price its products as low as possible.\n"We always try to keep our prices as low as possible, and fortunately we were able to lower the price of the iPhone this year," Cook said.\nThe iPhone 11's price was actually one of the bigger surprises of Apple's September event. Unlike the iPhone XR, which retailed for $749, the iPhone 11 starts at $699...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/8L3wW4e5cj4/tim-cook-interview-covers-iphone-11-price-apple-tv-and-more","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/tim-cook-interview-covers-iphone-11-price-apple-tv-and-more","type":"text/html"}],"crawled":1569966202021,"published":1569966019000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"keywords":["Technology","Apple Maps","iOS","iOS 13","Mac","macOS 10.14 Mojave","Maps"],"originId":"https://mjtsai.com/blog/?p=26750","fingerprint":"4621191","id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d89345b24:5125c:18991ffa","author":"Michael Tsai","summary":{"direction":"ltr","content":"Justin O’Beirne: This is the fifth time that Apple has expanded its new map since its public launch in September 2018[…] In June 2019, Apple announced that its new map would cover “the entire U.S. by the end of 2019”[…] With this latest expansion, Apple’s new map now covers 27.5% of the U.S.’s land area...and […]"},"alternate":[{"href":"https://mjtsai.com/blog/2019/10/01/apples-new-map-expands-to-northeast-u-s/","type":"text/html"}],"crawled":1569964972836,"title":"Apple’s New Map Expands to Northeast U.S.","published":1569961805000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","htmlUrl":"https://mjtsai.com/blog","title":"Michael Tsai"},"content":{"direction":"ltr","content":"

Justin O’Beirne:

\n
\n

This is the fifth time that Apple has expanded its new map since its public launch in September 2018[…]

\n

In June 2019, Apple announced that its new map would cover “the entire U.S. by the end of 2019”[…]

\n

With this latest expansion, Apple’s new map now covers 27.5% of the U.S.’s land area...and almost half of its population (47.2%)[…]

\n
\n

It definitely looks much improved in my area, though I still find Google Maps to be better.

\n

Previously:

\n"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"keywords":["Technology","Artificial Intelligence","Camera","iOS","iOS 13","iPhone 11","iPhone 11 Pro","Privacy"],"originId":"https://mjtsai.com/blog/?p=26748","fingerprint":"4b6bfbc2","id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d89345b24:5125b:18991ffa","author":"Michael Tsai","summary":{"direction":"ltr","content":"Matthew Panzarino: Deep Fusion is a technique that blends multiple exposures together at the pixel level to give users a higher level of detail than is possible using standard HDR imaging — especially in images with very complicated textures like skin, clothing or foilage. […] According to Apple, Deep Fusion requires the A13 and will […]"},"alternate":[{"href":"https://mjtsai.com/blog/2019/10/01/deep-fusion-beta/","type":"text/html"}],"crawled":1569964972836,"title":"Deep Fusion Beta","published":1569961791000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","htmlUrl":"https://mjtsai.com/blog","title":"Michael Tsai"},"content":{"direction":"ltr","content":"

Matthew Panzarino:

\n
\n

Deep Fusion is a technique that blends multiple exposures together at the pixel level to give users a higher level of detail than is possible using standard HDR imaging — especially in images with very complicated textures like skin, clothing or foilage.

\n

[…]

\n

According to Apple, Deep Fusion requires the A13 and will not be available on any older iPhones.

\n

As I spoke about extensively in my review of the iPhone 11 Pro, Apple’s ‘camera’ in the iPhone is really a collection of lenses and sensors that is processed aggressively by dedicated machine learning software run on specialized hardware. Effectively, a machine learning camera.

\n
\n

John Gruber:

\n
\n

Deep Fusion only works with the telephoto and regular wide lenses — it does not work with the ultra-wide lens. Because of that, Deep Fusion is not compatible with “Photos Capture Outside the Frame”, because the outside-the-frame content is usually captured with the ultra-wide lens. So I think we now have two reasons why “Photos Capture Outside the Frame” is not turned on by default[…]

\n

[…]

\n

Deep Fusion is not a mode or even an option like Night Mode is — it will simply apply automatically when the Camera app thinks it should.

\n
\n

Previously:

\n"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d893313be:f1:db1c1742","originId":"58069.pyogg0 at https://www.imore.com","fingerprint":"7bfb3f87","content":{"content":"

\n

What you need to know

\n
  • Apple recalled some 2015 MacBook Pro models.
  • \n
  • A British travel photographer's machine is one of those recalled.
  • \n
  • He hasn't had it repaired yet and now he can't fly.
  • \n

There are worse places to be stuck than Vietnam.

\n

A British travel photographer finds himself stuck in location in Vietnam after it turned out that his 15-inch MacBook Pro was one of those recalled by Apple. The problem is he didn't get the work done, and now no airline will let him fly.

\n

According to the story in The Independent the photographer was invited to work in Vietnam by a travel company. The flew there on an Air France airplane with his MacBook Pro in his carry on luggage. He even made two domestic flights with Vietnam Airlines without issue, but when he tried to return home via the same airline he was told that he couldn't take the notebook with him. At all.

\n
\n

At first they said 'don't switch it on during the flight'. But then security decided to say a flat 'no' to me taking it on the aircraft.

\n
\n

When it was suggested that the MacBook Pro remain in Vietnam while the man flew home to Europe, he refused. Apparently it was just too important to leave behind.

\n

Instead, he's now waiting for a replacement battery to be shipped out from Singapore so his machine can be repaired. That can take up to two weeks, apparently. Which we imagine is quite the inconvenience.

\n

Apple recalled some 2015 15-inch MacBook Pros earlier this year and ever since we've seen various airlines and aviation authorities take different stances on the situation. It may also come down to whether the security officials at the airline and check-in desks are fully up to speed as to whether an affected machine is allowed onto a flight, too.

\"\"","direction":"ltr"},"title":"Photographer stuck in Vietnam because of his recalled MacBook Pro","author":"Oliver Haslam","summary":{"content":"What you need to know\nApple recalled some 2015 MacBook Pro models.\nA British travel photographer's machine is one of those recalled.\nHe hasn't had it repaired yet and now he can't fly.\nThere are worse places to be stuck than Vietnam.\nA British travel photographer finds himself stuck in location in Vietnam after it turned out that his 15-inch MacBook Pro was one of those recalled by Apple. The problem is he didn't get the work done, and now no airline will let him fly.\nAccording to the story in The Independent the photographer was invited to work in Vietnam by a travel company. The flew there on an Air France airplane with his MacBook Pro in his carry on luggage. He even made two domestic flights with Vietnam Airlines without issue, but when he tried to return home via the same airline he was told that he couldn't take the notebook with him. At all.\nAt first they said 'don't switch it on during the flight'. But then security decided to say a flat 'no' to me taking it on the air...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/1uO5HHWGIoA/photographer-stuck-vietnam-because-his-recalled-macbook-pro","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/photographer-stuck-vietnam-because-his-recalled-macbook-pro","type":"text/html"}],"crawled":1569964889022,"published":1569964758000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/10/01/nanowrimo_is_next_month_and_i_will_conti","fingerprint":"8549ffd6","id":"+jHfsXnBCVfCstSIW1WDumAyigT4rnsUPnI5WFxgnAU=_16d89254704:5121b:18991ffa","summary":{"direction":"ltr","content":"

NaNoWriMo is next month, and I will continue my streak of not participating in it. I’m super-impressed by the people who do, though.

\n

It would take me a month of hard, solid work all November to decide on an idea to write about, then another month to think it through some more — or two months, really, because the holidays get in the way — and then about a year of nightly work to decide on a plot outline and characters and tone, and then another year of refining that outline, and then, by NaNoWriMo 2021 or 2022, I’d be ready to start writing. I suspect I’d average about 300 words a day, which would get me about 9,000 words for the month — which is well less than a novel or even the 50,000 words goal.

\n

I blog instead.

\n

PS What made me think of this: Cheri Baker, Let’s Half-Ass NaNoWriMo Together.

"},"alternate":[{"href":"https://inessential.com/2019/10/01/nanowrimo_is_next_month_and_i_will_conti","type":"text/html"}],"crawled":1569963984644,"published":1569960276000,"origin":{"streamId":"feed/http://ranchero.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/irlbanner-1382819058.jpg","width":620,"height":194,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/10/01/nanowrimo_is_next_month_and_i_will_conti","fingerprint":"8549ffd6","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d8924f769:5121a:18991ffa","summary":{"direction":"ltr","content":"

NaNoWriMo is next month, and I will continue my streak of not participating in it. I’m super-impressed by the people who do, though.

\n

It would take me a month of hard, solid work all November to decide on an idea to write about, then another month to think it through some more — or two months, really, because the holidays get in the way — and then about a year of nightly work to decide on a plot outline and characters and tone, and then another year of refining that outline, and then, by NaNoWriMo 2021 or 2022, I’d be ready to start writing. I suspect I’d average about 300 words a day, which would get me about 9,000 words for the month — which is well less than a novel or even the 50,000 words goal.

\n

I blog instead.

\n

PS What made me think of this: Cheri Baker, Let’s Half-Ass NaNoWriMo Together.

"},"alternate":[{"href":"https://inessential.com/2019/10/01/nanowrimo_is_next_month_and_i_will_conti","type":"text/html"}],"crawled":1569963964265,"published":1569960276000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/galaxygear-lead.jpg","width":619,"height":411,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","label":"Must Read"},{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"keywords":["Ballard"],"originId":"https://www.myballard.com/?p=115800","fingerprint":"6ff5b7a0","id":"E51hsZSss+6XSdMAYelFIdkn3CDBqFF2zAtZLRbxUrQ=_16d8924f6cc:51219:18991ffa","author":"Meghan Walker","summary":{"direction":"ltr","content":"The fourth open house to discuss options for the redevelopment of the large National Guard Armory site in Interbay is tonight (Oct. 1) at the National Nordic Museum. The state-owned site, located just behind the Whole Foods on 15th Ave W in Interbay, is being considered for redevelopment as it’s no longer an ideal location […]"},"alternate":[{"href":"https://www.myballard.com/2019/10/01/tonight-open-house-to-learn-about-future-plans-for-interbay-armory/","type":"text/html"}],"crawled":1569963964108,"title":"TONIGHT: Open house to learn about future plans for Interbay Armory","published":1569961669000,"origin":{"streamId":"feed/http://www.myballard.com/feed/","htmlUrl":"https://www.myballard.com","title":"My Ballard"},"content":{"direction":"ltr","content":"

The fourth open house to discuss options for the redevelopment of the large National Guard Armory site in Interbay is tonight (Oct. 1) at the National Nordic Museum.

\n

The state-owned site, located just behind the Whole Foods on 15th Ave W in Interbay, is being considered for redevelopment as it’s no longer an ideal location for the National Guard. The open house will be held by the Washington Department of Commerce Interbay Public Development Advisory Committee at the Nordic at 6pm.

\n
\"\"
\n

The advisory committee is tasked with studying possible reuses for the National Guard Interbay site, assuming the Guard will be able to relocate to a new site. The Interbay site was built in 1974, and the facility has been deemed insufficient. According to the Department of Commerce, it doesn’t meet current National Guard Readiness Center requirements for mission support. Over 600 personnel are based at the site, which they say isn’t ideal for commuting or deployment of large military equipment in and out of the city.

\n

This is the fourth open house held by Commerce. According to the department, the committee will provide a report to the legislature and the Office of the Governor with recommendations for each of these areas by Nov. 15.

\n

You can learn more about the project here.

\n

Thanks Angie Herb Gerrald for posting about the open house in the My Ballard Group!

"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d891f3096:d8:db1c1742","originId":"58067.pyogg0 at https://www.imore.com","fingerprint":"709a3f49","content":{"content":"

Trading Wakanda for the basketball court.

\n

\n

What you need to know

\n
  • Winston Duke will allegedly star in a new Apple TV+ series called Swagger.
  • \n
  • The show is based on the youth basketball experiences of NBA star Kevin Durant.
  • \n
  • Apple TV+ launches November 1 for $4.99 per month.
  • \n

Winston Duke, who played the mighty M'Baku in Marvel's Black Panther, will reportedly play the lead in an upcoming Apple TV+ series called Swagger.

\n

According to Variety, the show is inspired by Kevin Durant's youth basketball experiences, chronicling how family, players, and coaches all intersect. Duke will allegedly play a youth coach named Ike.

\n

Duke rose to fame with his turn in Black Panther, when he played a rival tribe leader named M'Baku; he also had brief cameos in Avengers: Infinity War and Avengers: Endgame. Duke then had a starring role in Jordan Peele's Us.

\n

Swagger doesn't currently have a release date, but it joins an illustrious list of shows headed to Apple TV+, which also includes The Morning Show, Dickinson, and For All Mankind.

\n

Apple TV+ is slated to launch on November 1 for $4.99 per month.

\n

Everything you need to know about Apple TV+

\"\"","direction":"ltr"},"title":"Black Panther actor to star in Kevin Durant Apple TV+ series","author":"Brandon Russell","summary":{"content":"Trading Wakanda for the basketball court.\nWhat you need to know\nWinston Duke will allegedly star in a new Apple TV+ series called Swagger.\nThe show is based on the youth basketball experiences of NBA star Kevin Durant.\nApple TV+ launches November 1 for $4.99 per month.\nWinston Duke, who played the mighty M'Baku in Marvel's Black Panther, will reportedly play the lead in an upcoming Apple TV+ series called Swagger.\nAccording to Variety, the show is inspired by Kevin Durant's youth basketball experiences, chronicling how family, players, and coaches all intersect. Duke will allegedly play a youth coach named Ike.\nDuke rose to fame with his turn in Black Panther, when he played a rival tribe leader named M'Baku; he also had brief cameos in Avengers: Infinity War and Avengers: Endgame. Duke then had a starring role in Jordan Peele's Us.\nSwagger doesn't currently have a release date, but it joins an illustrious list of shows headed to Apple TV+, which also includes The Morning Show, ...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/HTeTO--d46s/black-panther-actor-star-kevin-durant-apple-tv-series","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/black-panther-actor-star-kevin-durant-apple-tv-series","type":"text/html"}],"crawled":1569963585686,"published":1569963011000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d891f3096:d7:db1c1742","originId":"58066.pyogg0 at https://www.imore.com","fingerprint":"f7ad6ce4","content":{"content":"

\n

WARP is a VPN but it's not like most others and doesn't work the way you think a VPN should. It's confusing.

\n

In November 2018, Cloudflare introduced its 1.1.1.1 application. It was a simple app that could move your phone's networking stack to use Cloudflare's 1.1.1.1 DNS service instead of the one assigned by your internet service provider. Google does something very similar with its own public DNS service, but Cloudflare's is faster so it made your connections feel more "instant." The announcement also talked about something called WARP, which would be coming at a later date.

\n
\n

WARP is part of Cloudflare's existing app and not a standalone service.

\n
\n

That later date has arrived, and WARP is now part of the 1.1.1.1 app. So far, not too confusing, but that changes when the term VPN enters the fray. WARP is a VPN, but WARP isn't like any VPN you might be using now or have heard about.

\n

What is DNS?

\n

\n

DNS (Domain Name System) is used to convert familiar names like www.google.com into IP (Internet Protocol) addresses like 173.194.39.78.

\n

Your phone (or any other computer) can only connect to anything over the internet using numbers. DNS servers are used to translate the things we type into those numbers so we don't have to memorize them. They're often called the phone book of the internet. These servers, called nameservers, hold files that can look up names like microsoft.com or facebook.com so you can be connected when you type them into your browser or when an app calls for a site to be opened. There is no single computer that holds the billions of DNS records — instead, they are distributed on many machines all over the globe.

\n
\n

Nameservers are the internet's phone book. Remember those?

\n
\n

The way nameservers are mapped means there are machines that hold lookup abilities for each TLD (Top Level Domain), which in turn has its own set of nameservers that has individual records. A TLD is the three letters like "com" or "net" that end a URL you type in.

\n

So you can imagine just how many different machines are used to look up what we all type and convert it into a string of numbers that our phones and computers can use to actually connect to anything on the internet. You can also imagine that some of these machines are super busy and can be slow. If you've ever typed in a web address and waited for a page to change or load, part of that waiting time was probably for the DNS resolution. Having nameservers that are not only faster but don't track requests, like Google or Cloudflare's offerings, is a really good thing.

\n

What is a VPN?

\n

You might think a VPN (Virtual Private Network) is a network tool that keeps you more private on the internet or lets you access region-restricted content, but those are actually byproducts and not at all what a VPN was designed to do. A VPN was "invented" to allow you a direct and secure connection to a remote network through the internet. An example would be me using a VPN to connect to Mobile Nation's servers from anywhere and having the traffic tunneled through the regular internet without interacting with any of it. That's a real example we use every day, by the way.

\n

More: Save money with these great VPN deals

\n

A VPN doesn't even have to be encrypted and doesn't even hide your traffic — it simply changes who can see it. There are many great VPNs that do encrypt and anonymize your internet traffic, letting you browse the web privately and access all of its content. But those aren't the only kinds of VPNs and that can be confusing.

\n

What is WARP?

\n

\n

WARP is a VPN that doesn't hide your origin IP (where or who you are) but does encrypt your traffic and use Cloudflare's 1.1.1.1 DNS service. It's rolled into the 1.1.1.1 app and shouldn't be considered a separate thing. The 1.1.1.1 app protects your DNS queries from being "sniffed" on local and unsecured networks, like the Wi-Fi router at your local Starbucks, and when WARP is activated from inside the app it adds a VPN encryption layer that adds to that protection.

\n
\n

A VPN can bolster your online privacy, but that wasn't Cloudflare's original intent.

\n
\n

This is all the 1.1.1.1 app does, and it's not any good for hiding your location or browsing anonymously. It's not advertised to be either, and the terms of service even tell you that your original IP (the one your ISP gave you) is being reported to Cloudflare servers. Cloudflare also says it keeps that data for two years and never sells any of it.

\n

1.1.1.1 with WARP is free to use, but Cloudflare also offers a premium package called WARP+. WARP+ offers the same features as the free version with one handy extra — requests over the internet are routed through Cloudflare's network. Using a feature known as Argo Smart Routing, WARP+ can make sure the things you want to see aren't affected by network congestion. WARP+ pricing varies slightly based on location, which Cloudflare says was done to make sure it matches the cost of a Big Mac.

\n

If this isn't what you're looking for, don't use Cloudflare's DNS and instead use the one your ISP provides or Google's in tandem with another VPN product. There are plenty of great ones to choose from.

\n

Should I use this?

\n

It's not going to hurt anything and it's not doing anything "bad" regardless of what you might have read on the internet. The problem is that Cloudflare uses the words "VPN" and "not a VPN" interchangeably and that makes the whole thing confusing as heck. Even folks who know how all of this works were a bit confused and had to figure out exactly what Cloudflare was doing and what it was offering with the addition of WARP to the 1.1.1.1 app.

\n

If you're not concerned about what DNS is or does and how nameservers or encryption work, think of the app as a little extra protection while on the internet, but not any sort of service that can hide your location or identity and you'll have a good handle on it all. Read the terms of service before you give the app the OK to start working and decide if its something you need.

\n\n

Warp VPN

\n

\n

Free at App Store

\n

Cloudflare's VPN isn't a traditional VPN — it encrypts your data without hiding your origin, so it's not meant to be used to access geographically-restricted content or to get around other restrictions. At its core, it's just meant to make your public browsing safer and faster.

\n
\"\"","direction":"ltr"},"title":"What is Cloudflare's WARP VPN and should you use it?","author":"Jerry Hildenbrand","summary":{"content":"WARP is a VPN but it's not like most others and doesn't work the way you think a VPN should. It's confusing.\nIn November 2018, Cloudflare introduced its 1.1.1.1 application. It was a simple app that could move your phone's networking stack to use Cloudflare's 1.1.1.1 DNS service instead of the one assigned by your internet service provider. Google does something very similar with its own public DNS service, but Cloudflare's is faster so it made your connections feel more "instant." The announcement also talked about something called WARP, which would be coming at a later date.\nWARP is part of Cloudflare's existing app and not a standalone service.\nThat later date has arrived, and WARP is now part of the 1.1.1.1 app. So far, not too confusing, but that changes when the term VPN enters the fray. WARP is a VPN, but WARP isn't like any VPN you might be using now or have heard about.\nWhat is DNS?\nDNS (Domain Name System) is used to convert familiar names like www.google.com int...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/RFkBszWe0bk/what-cloudflares-warp-and-should-you-use-it","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/what-cloudflares-warp-and-should-you-use-it","type":"text/html"}],"crawled":1569963585686,"published":1569962978000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=613165","fingerprint":"79e3e2f0","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d891b096a:511c9:18991ffa","author":"Michael Potuck","summary":{"direction":"ltr","content":"
\n

Apple CEO, Tim Cook, has been visiting Apple Stores, Apple developers, and more on a trip to Germany and France. Now, in a new interview, Cook has shared more details about the iPhone 11 being more affordably priced, how he’s looking at Apple TV+ and its competition, concerns about Apple being a monopoly, and more.

\n

more…

\n

The post Tim Cook talks lower-priced iPhone 11, Apple TV+ competition, more in new interview appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/10/01/tim-cook-interview-germany-iphone-11-pricing/","type":"text/html"}],"crawled":1569963313514,"title":"Tim Cook talks lower-priced iPhone 11, Apple TV+ competition, more in new interview","published":1569960715000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d89066ab8:c5:db1c1742","originId":"58065.pyogg0 at https://www.imore.com","fingerprint":"36db3a5f","content":{"content":"

\n

What you need to know

\n
  • With Apple set to release Deep Fusion via an iOS 13 developer beta update, lets take a look at what it said about the feature during the iPhone event.
  • \n
  • Phil Schiller revealed it uses the nueral engine to create a new image processing system.
  • \n
  • He calls it "computational photography mad science."
  • \n

It uses the neural engine of the A13 Bionic to create a whole new image processing system.

\n

When Apple unveiled the iPhone 11 and its fancy new cameras, it also gave a sneak peek for a brand new camera feature it would offer: Deep Fusion. Now that the feature is rolling out with the latest iOS 13 developer beta, we decided to look back and see exactly what Apple said about the feature during the September 10 event.

\n

Apple vice president of Worldwide Marketing Phil Schiller starts by stating it uses "the neural engine of the A13 Bionic to create a whole brand new kind of image processing system."

\n

Then he presents an image of a man seating on a couch with a vividly intertwined sweater that has a lot of intricate detail in the weaving pattern. He says this type of image would not have been possible before.

\n

\n

Using machine learning, this image is captured in low to medium light. Here's how it does that according to Schiller.

\n
\n

It shoots nine images. Before you press the shutter button, it's already shot four short images [and] four secondary images. When you take the shutter button, it takes one long exposure. And in just one second, the neural engine analyzes the fused combination of long and short images picking the best among them, selecting all the pixels and pixel by pixel, going through 24 million pixels, to optimize for detail and low noise.

\n

This is the first time a neural engine is responsible for generating the output image. It is computational photography mad science. It's way cool.

\n
\n

And that was it. Apple didn't get into too much detail as the feature would not be available until a later time. It doesn't even mention it on its iPhone 11 page, but we imagine that will change shortly after the feature becomes available.

\n

The Deep Fusion process sounds daunting and amazing. The images Apple has released so far give us a good luck at the raw potential of the feature that takes full advantage of the new cameras in the iPhone 11. It could help further seperate the iPhone 11 camera from the competition.

\"\"","direction":"ltr"},"title":"Here’s everything Apple said about Deep Fusion during the iPhone event","author":"Danny Zepeda","summary":{"content":"What you need to know\nWith Apple set to release Deep Fusion via an iOS 13 developer beta update, lets take a look at what it said about the feature during the iPhone event.\nPhil Schiller revealed it uses the nueral engine to create a new image processing system.\nHe calls it "computational photography mad science."\nIt uses the neural engine of the A13 Bionic to create a whole new image processing system.\nWhen Apple unveiled the iPhone 11 and its fancy new cameras, it also gave a sneak peek for a brand new camera feature it would offer: Deep Fusion. Now that the feature is rolling out with the latest iOS 13 developer beta, we decided to look back and see exactly what Apple said about the feature during the September 10 event.\nApple vice president of Worldwide Marketing Phil Schiller starts by stating it uses "the neural engine of the A13 Bionic to create a whole brand new kind of image processing system."\nThen he presents an image of a man seating on a couch with a vividly intertwi...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/G6fI7_uaX-A/heres-everything-apple-said-about-deep-fusion-during-iphone-event","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/heres-everything-apple-said-about-deep-fusion-during-iphone-event","type":"text/html"}],"crawled":1569961962168,"published":1569961892000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://www.fubiz.net/wp-content/uploads/2013/10/Mucho-Macho-Character-Design-9.jpg","width":640,"height":746,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d88f67b14:b8:db1c1742","originId":"58064.pyogg0 at https://www.imore.com","fingerprint":"d75b2119","content":{"content":"

\n

Apple's Deep Fusion will give you the best photos possible, but you'll need to make sure one specific setting is turned off to use it.

\n

Apple announced Deep Fusion at the September iPhone event this year, but it did not launch in iOS 13 with the iPhone 11 and iPhone 11 Pro models. However, it appears that Deep Fusion is rolling out in the latest developer beta of iOS 13.2 on iPhone 11 devices, and will be available to everyone with an iPhone 11 soon.

\n

While Deep Fusion happens automatically and won't be noticeable like Night Mode, it turns out that it won't be enabled if you have the Capture Outside of Frame setting turned on (here's how to enable Capture Outside of Frame if you missed it). So if you have Capture Outside of Frame turned on, Deep Fusion will not be available, and vice versa.

\n

What is Deep Fusion and how will it work?

\n

\n

Deep Fusion is a new computational photography process specifically on the iPhone 11 line because of the A13 chip. It blends together multiple exposures at the pixel level in order to create a photograph with an even higher level of detail than standard HDR. This means even more detailed textures in things like skin, clothing, and foliage.

\n

Unlike Night Mode, Deep Fusion will happen automatically and we won't even notice it. However, it will only activate in certain situations.

\n

With the Wide (Normal) lens, Deep Fusion kicks in at just above the 10 lux floor that triggers Night Mode. Whether Night Mode or Deep Fusion activates depends on the lighting source for the current scene. On the Telephoto camera, Deep Fusion will be active in pretty much everything except the brightest situations, as that is when the Smart HDR goes into effect due to the abundance of highlights.

\n

Essentially, Deep Fusion has the camera shoot a short frame at a negative EV value. This results in a darker image than you would normally like, but it just pulls sharpness from this frame. It also shoots three regular EV0 images and then a long EV+ frame, aligns everything together, and blends all of the images into one.

\n

You end up with two 12MP images that are combined together in a single 24MP photo. The process to do this uses four separate neural networks, which account for all of the noise characteristics of the camera sensors, as well as the photo subject matter.

\n

The machine learning process looks at every individual pixel before combining, and it only takes about one second to process everything. Deep Fusion will happen in the background, and you'll never really notice it until you see the images it produces.

\n

What is Capture Outside of Frame?

\n

Capture Outside of Frame is a setting available on iPhone 11 and iPhone 11 Pro that lets you capture content outside of the frame on the viewfinder. This captured content only appears when you make edits to the photo, such as cropping, straightening, rotating, and adjusting perspective. For QuickTake video capture, it helps improve the composition.

\n

So why won't Deep Fusion work with Capture Outside of Frame?

\n

\n

The Capture Outside of Frame feature works by utilizing the Ultra Wide camera to capture the rest of the scene outside of the frame. However, Deep Fusion does not work with the Ultra Wide camera due to the lack of focus pixels and optical image stabilization (this is also why Night Mode does not work with the Ultra Wide lens).

\n

So if you want to compare how effective Deep Fusion is, take a picture with Deep Fusion on, and then turn on Capture Outside of Frame and take the same photo.

\n

Are you looking forward to Deep Fusion?

\n

We are definitely excited that Deep Fusion is finally starting to roll out, and we eagerly anticipate the final release hopefully soon. Are you looking forward to Deep Fusion or have any questions about it? Let us know in the comments!

\n

Get More iPhone

\n

Apple iPhone

\n

\n

iPhone 11 Pro From $999 at Apple\niPhone 11 from $699 at Apple

\n

\n\n

\"\"","direction":"ltr"},"title":"Deep Fusion will only work if you disable Capture Outside of Frame","author":"Christine Chan","summary":{"content":"Apple's Deep Fusion will give you the best photos possible, but you'll need to make sure one specific setting is turned off to use it.\nApple announced Deep Fusion at the September iPhone event this year, but it did not launch in iOS 13 with the iPhone 11 and iPhone 11 Pro models. However, it appears that Deep Fusion is rolling out in the latest developer beta of iOS 13.2 on iPhone 11 devices, and will be available to everyone with an iPhone 11 soon.\nWhile Deep Fusion happens automatically and won't be noticeable like Night Mode, it turns out that it won't be enabled if you have the Capture Outside of Frame setting turned on (here's how to enable Capture Outside of Frame if you missed it). So if you have Capture Outside of Frame turned on, Deep Fusion will not be available, and vice versa.\nWhat is Deep Fusion and how will it work?\nDeep Fusion is a new computational photography process specifically on the iPhone 11 line because of the A13 chip. It blends together multiple exposur...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/6LTkhTUllz4/youll-need-disable-capture-outside-frame-use-deep-fusion-iphone-11","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/youll-need-disable-capture-outside-frame-use-deep-fusion-iphone-11","type":"text/html"}],"crawled":1569960917780,"published":1569960882000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://cdn1.sbnation.com/assets/3360827/SE-sizzle.png","width":1100,"height":619,"contentType":"image/png"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"tag:daringfireball.net,2019:/linked//6.36094","recrawled":1569971240153,"updateCount":1,"fingerprint":"8bd99e59","id":"SfSSQbsJpAnFmSl8cDkL3bQGKFOfsp48QsvCmTUjxYM=_16d88edb8a9:510a6:18991ffa","updated":1569968484000,"author":"John Gruber","alternate":[{"href":"https://techcrunch.com/2019/10/01/apple-launches-deep-fusion-feature-in-beta-on-iphone-11-and-iphone-11-pro/","type":"text/html"}],"crawled":1569960343721,"title":"Deep Fusion Coming to iPhones 11 in iOS 13.2 Beta 1","published":1569957535000,"origin":{"streamId":"feed/http://daringfireball.net/feeds/main","htmlUrl":"https://daringfireball.net/","title":"Daring Fireball"},"content":{"direction":"ltr","content":"

Matthew Panzarino, writing at TechCrunch:

\n
\n

Apple is launching an early look at its new Deep Fusion feature on\niOS today with a software update for beta users. Deep Fusion is a\ntechnique that blends multiple exposures together at the pixel\nlevel to give users a higher level of detail than is possible\nusing standard HDR imaging — especially in images with very\ncomplicated textures like skin, clothing or foliage.

\n
\n

It requires the A13 chip, so it’s iPhones 11-only. I spoke with Apple this morning about it, and Panzarino’s description of how Deep Fusion works matches my notes exactly. Just read his write-up.

\n

Here’s an interesting tidbit: Deep Fusion only works with the telephoto and regular wide lenses — it does not work with the ultra-wide lens. Because of that, Deep Fusion is not compatible with “Photos Capture Outside the Frame”, because the outside-the-frame content is usually captured with the ultra-wide lens. So I think we now have two reasons why “Photos Capture Outside the Frame” is not turned on by default:

\n
    \n
  1. Apple believes that Deep Fusion will improve more photos for more users than Capture Outside the Frame will, so Capture Outside the Frame is off by default. Deep Fusion is not a mode or even an option like Night Mode is — it will simply apply automatically when the Camera app thinks it should. For the wide angle lens, that’s in mid-range indoor lighting conditions; for the telephoto, Deep Fusion will be applied in all but the brightest outdoor conditions. (So, if you want to compare the effect of Deep Fusion, one way to do it is to capture the same scene with and without “Photos Capture Outside the Frame” enabled — only when it’s disabled will Deep Fusion kick in.)

  2. \n
  3. Privacy. Someone framing a still photo might have something outside the frame they would not want captured — anything from a shirtless portrait where the ultra-wide image would reveal the subject is pantsless as well, to an object on your desk or countertop where the ultra-wide image might reveal an envelope with your home address.

  4. \n
\n"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://www.raywenderlich.com/2942532-concurrency-by-tutorials","fingerprint":"33b56b05","id":"RFlzskW4NhJjlZfijOSI8IXqM9+zz6V9qnDVl1gxaJs=_16d88ed77ca:510a4:18991ffa","updated":1569958464000,"summary":{"direction":"ltr","content":"The book that teaches you everything there is to know about how to write performant and concurrent code for your apps."},"alternate":[{"href":"https://www.raywenderlich.com/2942532-concurrency-by-tutorials","type":"text/html"}],"crawled":1569960327114,"title":"Concurrency by Tutorials [SUBSCRIBER]","published":1569958464000,"origin":{"streamId":"feed/http://www.raywenderlich.com/feed","htmlUrl":"http://www.raywenderlich.com/feed","title":"Ray Wenderlich | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"originId":"https://jvns.ca/blog/2019/10/01/zine-revenue-2019/","recrawled":1569966713792,"updateCount":1,"fingerprint":"d93396c7","id":"wEPPtpyxm18xeuKJtd0RXOmRfdiYTcxSNO1zsjNlPdg=_16d88e0db64:5104a:18991ffa","updated":1569934438000,"alternate":[{"href":"https://jvns.ca/blog/2019/10/01/zine-revenue-2019/","type":"text/html"}],"crawled":1569959500644,"title":"Zine revenue for 2019","published":1569934438000,"origin":{"streamId":"feed/http://jvns.ca/atom.xml","htmlUrl":"http://jvns.ca","title":"Julia Evans"},"content":{"direction":"ltr","content":"

I occasionally get questions like “Can you share what you’ve learned about\nrunning a business?” The most surprising thing I’ve learned is that it’s\npossible to make money by teaching people computer things on the internet, so I\nwant to make that a little more concrete by sharing the revenue from the zine\nbusiness so far in 2019. Here’s a graph of revenue by\nmonth (the last month is September 2019):

\n

\n

This adds up to $87,858 USD for 2019 so far, which (depending on what I release in the\nrest of this year) is on track to be similar to revenue for 2018 ($101,558).

\n

Until quite recently I’d been writing zines in my spare time, and now I’m taking a year to focus on it.

\n

how $30,000 for September breaks down

\n

The most obvious thing in that monthly revenue graph above is that 2 months\n(September and March) have way more revenue than all the others. This is\nbecause I released new zines (Bite Size Networking and HTTP: Learn your browser’s\nlanguage) in those months.

\n

Here’s how the $30,000 for September breaks down:

\n
    \n
  • it’s 85% sales to individuals, 15% corporate licenses
  • \n
  • it’s approximately:\n
      \n
    • $18,000 the new HTTP zine
    • \n
    • $10,000 the various zine packs (the 6 pack and the 3 pack)
    • \n
    • $2,000 other individual zines
    • \n
  • \n
\n

This September was the month with the most sales ever, which is mostly because\nof individual humans who find the zines useful (thank you!!).

\n

expenses

\n

The main expenses are paying illustrators and an accountant, a mailing list,\nand various books I buy to learn how to do things better. They probably come\nout to about 10% of revenue or so, and then there are taxes after that.

\n

giving away free copies has been great

\n

With the HTTP zine, like many of my previous zines, I’ve been giving away one\nfree copy for every copy that people buy, so that people can get it even if $12\nis hard for them to afford. (if you can’t afford $12, here’s the\nlink, there are about 70\navailable as I’m writing this). I’m pretty happy with this setup – we’ve given\naway 1358 copies so far. (I think of this as kind of a “sales” statistic too)

\n

I think I want to automate the system to give away free copies a bit more soon\n(like by automatically updating the number of free zines available using the\nGumroad API instead of periodically doing it manually).

\n

hopefully this is a useful data point!

\n

Writing about money on the internet is weird, so this will probably be the\nfirst and last zine revenue post, but I’m writing it down in the hopes that\nit’s a useful data point for others. I thought for a long time that you could\nonly really make money from writing on the internet with ads or sponsorships,\nbut it’s not true!

\n

The goal of this isn’t to say “you should run a business” or anything, just\nthat this is a thing that’s possible in the world and that many developers\ndo really value good educational materials and are happy to pay for them (if\nyou’re one of those people, thank you!)

"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/irlbanner-1382819058.jpg","width":620,"height":194,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"tag:daringfireball.net,2019:/linked//6.36094","recrawled":1569980785874,"updateCount":2,"fingerprint":"157cfb14","id":"ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16d88da4abd:50ff8:18991ffa","updated":1569979305000,"author":"John Gruber","alternate":[{"href":"https://techcrunch.com/2019/10/01/apple-launches-deep-fusion-feature-in-beta-on-iphone-11-and-iphone-11-pro/","type":"text/html"}],"crawled":1569959070397,"title":"Deep Fusion Coming to iPhones 11 in iOS 13.2 Beta 1","published":1569957535000,"origin":{"streamId":"feed/http://daringfireball.net/index.xml","htmlUrl":"https://daringfireball.net/","title":"Daring Fireball"},"content":{"direction":"ltr","content":"

Matthew Panzarino, writing at TechCrunch:

\n
\n

Apple is launching an early look at its new Deep Fusion feature on\niOS today soon with a software update for beta\nusers. Deep Fusion is a technique that blends multiple exposures\ntogether at the pixel level to give users a higher level of detail\nthan is possible using standard HDR imaging — especially in\nimages with very complicated textures like skin, clothing or\nfoliage.

\n
\n

It requires the A13 chip, so it’s iPhones 11-only. I spoke with Apple this morning about it, and Panzarino’s description of how Deep Fusion works matches my notes exactly. Just read his write-up.

\n

Here’s an interesting tidbit: Deep Fusion only works with the telephoto and regular wide lenses — it does not work with the ultra-wide lens. Because of that, Deep Fusion is not compatible with “Photos Capture Outside the Frame”, because the outside-the-frame content is usually captured with the ultra-wide lens. So I think we now have two reasons why “Photos Capture Outside the Frame” is not turned on by default:

\n
    \n
  1. Apple believes that Deep Fusion will improve more photos for more users than Capture Outside the Frame will, so Capture Outside the Frame is off by default. Deep Fusion is not a mode or even an option like Night Mode is — it will simply apply automatically when the Camera app thinks it should. For the wide angle lens, that’s in mid-range indoor lighting conditions; for the telephoto, Deep Fusion will be applied in all but the brightest outdoor conditions. (So, if you want to compare the effect of Deep Fusion, one way to do it is to capture the same scene with and without “Photos Capture Outside the Frame” enabled — only when it’s disabled will Deep Fusion kick in.)

  2. \n
  3. Privacy. Someone framing a still photo might have something outside the frame they would not want captured — anything from a shirtless portrait where the ultra-wide image would reveal the subject is pantsless as well, to an object on your desk or countertop where the ultra-wide image might reveal an envelope with your home address.

  4. \n
\n"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/YVzEWS7-wyeXrsbzBSknIPl0cOM=/215x0:2333x1412/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59630315/Screen_Shot_2018_05_04_at_10.28.16_AM.0.png","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"tag:daringfireball.net,2019:/linked//6.36093","fingerprint":"f7a11c87","id":"ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16d88da4abd:50ff7:18991ffa","updated":1569956119000,"author":"John Gruber","alternate":[{"href":"https://medium.com/@adamsandwich/were-sandwich-now-8b6895c5608c","type":"text/html"}],"crawled":1569959070397,"title":"Sandwich","published":1569955917000,"origin":{"streamId":"feed/http://daringfireball.net/index.xml","htmlUrl":"https://daringfireball.net/","title":"Daring Fireball"},"content":{"direction":"ltr","content":"

Adam Lisagor:

\n
\n

We used to be Sandwich Video. In fact, we’ve been Sandwich Video\nsince 2010, officially. But today, I’m so proud to announce our\nnew name. A shorter name. Leaner, more agile. Why? Just feels\nright. […]

\n

Eventually the ambiguity wore off and Sandwich Video had\nestablished itself as the upstart little production company for\nhot new tech companies to get great bespoke videos. We called them\n“videos” then because what else could they be? Demos? Promos?\nProbably not “commercials” and definitely not “content”. We made\nvideos for clients, and our output had its own built-in\nsubgenre: if you went to Sandwich Video, you ended up with a\nSandwich video. And our style was distinct, so everybody knew it\nwas a Sandwich video. Video video video.

\n
\n

I absolutely love the new Sandwich logo. It’s just perfect. It looks great, it fits the feel of the company to a T, and there’s a timelessness to it. Fun without being goofy or silly is a hard thing to pull off in a logo, but this mark does it. They could be using this logo decades from now and it’ll still look right. The new website is a model of good design and honest copywriting. (Don’t miss Agency Mode.)

\n

See also: Armin Vit on the new logo at Brand New:

\n
\n

I don’t even know why I am over-rationalizing this… it made me\nsmile, it made me happy, and it makes me want a sandwich.

\n
\n"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/YVzEWS7-wyeXrsbzBSknIPl0cOM=/215x0:2333x1412/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59630315/Screen_Shot_2018_05_04_at_10.28.16_AM.0.png","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d88d1c100:a3:db1c1742","originId":"58056.pyogg0 at https://www.imore.com","fingerprint":"2e13b8e7","content":{"content":"

\n

New information on features in Pokemon Sword and Shield have been revealed.

\n

What you need to know

\n
  • Gameinformer has released several articles on new features and updates for Pokémon Sword and Shield.
  • \n
  • These include confirming autosave, HMs not returning, exp. sharing and more.
  • \n
  • Pokémon Sword and Shield are aiming to be available on November 15.
  • \n
  • You can preorder Pokémon Sword or Pokémon Shield for $60 from Amazon.
  • \n

Pokémon Sword and Shield are on the way. Ahead of the games' release, Gameinformer has released several new articles covering new features in Pokémon Sword and Shield. There's lots of nifty new features or pieces of information that fans will appreciate.

\n

Here's an overview of the new information on features that are coming:

\n
  • Autosave - you don't need to save after every encounter anymore.
  • \n
  • Pokémon cut from Pokémon Sword and Shield may return for future games.
  • \n
  • HMs (Hidden Machines) have been removed.
  • \n
  • EXP. share - experience earned is automatically shared through all your Pokémon.
  • \n
  • While Game Freak are being vague, there are systems in place that will better allow you to use your favorite Pokémon competitively.
  • \n

Pokémon Sword and Shield are expected to release on November 15 exclusively for the Nintendo Switch.

\n

Pokémon Sword and Shield: Release date, trailers, & announcements

\n\n

Take up your Sword

\n

Pokémon Sword

\n

\n

$60 at Amazon

\n

Welcome to the Galar region

\n

Pokémon Sword is the first half of the Pokémon games releasing this fall. With new Pokémon to tame and a legendary Pokémon exclusive to this version, there'll be plenty of Trainers to explore and do.

\n

\n

\n

Or take up your Shield

\n

Pokémon Shield

\n

\n

$60 at Amazon

\n

Welcome to the Galar region

\n

Pokémon Shield is the second half of the Pokémon games that will be releasing this fall. With new Pokémon to tame and a legendary Pokémon exclusive to this version, there'll be plenty of Trainers to explore and do.

\n\n

Get More Switch

\n

Nintendo Switch

\n

\n\n\n\n\n\n\n\n \n

\n

\n

$299 at Amazon

\n
\"\"","direction":"ltr"},"title":"New info on autosave, HMs, exp. share and more for Pokémon Sword and Shield","author":"Samuel Tolbert","summary":{"content":"New information on features in Pokemon Sword and Shield have been revealed.\nWhat you need to know\nGameinformer has released several articles on new features and updates for Pokémon Sword and Shield.\nThese include confirming autosave, HMs not returning, exp. sharing and more. Pokémon Sword and Shield are aiming to be available on November 15. You can preorder Pokémon Sword or Pokémon Shield for $60 from Amazon. Pokémon Sword and Shield are on the way. Ahead of the games' release, Gameinformer has released several new articles covering new features in Pokémon Sword and Shield. There's lots of nifty new features or pieces of information that fans will appreciate.\nHere's an overview of the new information on features that are coming:\nAutosave - you don't need to save after every encounter anymore. Pokémon cut from Pokémon Sword and Shield may return for future games.\nHMs (Hidden Machines) have been removed. EXP. share - exp...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/MbtAVtUGbzk/pokemon-sword-and-shield-getting-autosave-hms-wont-return-and-more","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/pokemon-sword-and-shield-getting-autosave-hms-wont-return-and-more","type":"text/html"}],"crawled":1569958510848,"published":1569958414000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://www.fubiz.net/wp-content/uploads/2013/10/Mucho-Macho-Character-Design-9.jpg","width":640,"height":746,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d88ce0d5a:a0:db1c1742","originId":"58060.pyogg0 at https://www.imore.com","fingerprint":"ce684587","content":{"content":"

Never lose track of your AirPods again.

\n

\n

What you need to know

\n
  • Jura Anchor is an attachment for your AirPods.
  • \n
  • Anchor uses a proprietary connector to attach to your AirPods case via the Lightning port.
  • \n
  • Achor is available now on Kickstarter and ships in November.
  • \n

Carrying an AirPods case is a breeze becase of its size, making it easy to put in a bag or pocket. But, in some scenarios, the size can actually backfire, because keeping track of their whereabouts can be difficult. That's where the Jura Anchor comes in.

\n

Created by Patrick O'Neill, the mind behind Olloclip, Anchor adds a carabiner to your AirPods, so they're even easier to carry while out and about. Crucially, attaching Anchor to your AirPods case means you'll always know where they are.

\n

\n

"Following Olloclip's success, I'm excited to launch Jura with this first offering," O'Neill said in a press release. "As a big Apple fan, I know the struggle of digging through a bag or pockets for my AirPods when a call comes in or I want to listen to music. Unlike other options - like a whole case with clip for the charging case - the Anchor doesn't complicate AirPod usability, create unneeded bulk, or cover the hinged cover or lightning port of the case as it provides one-handed accessibility to AirPods."

\n

O'Neill said he created a proprietary connector featuring "high strength composite wings to help prevent side load damage." When Anchor is attached to an AirPods case through the Lightning port, it can apparently support 15 times the weight of an AirPods case, so there should be no issue of the attachment suddenly coming off.

\n

Jura is offering four different options on its Kickstarter, which runs through the end of October. $19 will land you an Anchor in anodized zinc alloy in silver, gray, or black. You can also buy Anchor in titanium for $29. There are packs, too, including a two-pack of Anchor in titanium for $53.

\n

Jura has already smashed its goal of raising $30,000, and with nearly 800 backers and growing, it's clear Anchor is an AirPods accessory a lot of people have been waiting for.

\n

AirPods accessory

\n

Jura Anchor

\n

\n

Always know where your AirPods are with the new Jura Anchor, which uses a clever proprietary connector to attach a carabiner to your case. It's the last and only AirPods accessory you'll ever need.

\n

$19 at Kickstarter

\"\"","direction":"ltr"},"title":"Jura Anchor is the ultimate AirPods accessory","author":"Brandon Russell","summary":{"content":"Never lose track of your AirPods again.\nWhat you need to know\nJura Anchor is an attachment for your AirPods.\nAnchor uses a proprietary connector to attach to your AirPods case via the Lightning port.\nAchor is available now on Kickstarter and ships in November.\nCarrying an AirPods case is a breeze becase of its size, making it easy to put in a bag or pocket. But, in some scenarios, the size can actually backfire, because keeping track of their whereabouts can be difficult. That's where the Jura Anchor comes in.\nCreated by Patrick O'Neill, the mind behind Olloclip, Anchor adds a carabiner to your AirPods, so they're even easier to carry while out and about. Crucially, attaching Anchor to your AirPods case means you'll always know where they are.\n"Following Olloclip's success, I'm excited to launch Jura with this first offering," O'Neill said in a press release. "As a big Apple fan, I know the struggle of digging through a bag or pockets for my AirPods when a call comes in or I wa...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/8Sg3S2tNJEY/jura-anchor-ultimate-airpods-accessory","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/jura-anchor-ultimate-airpods-accessory","type":"text/html"}],"crawled":1569958268250,"published":1569957948000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://b.vimeocdn.com/ts/452/218/452218069_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16d88c41716:9a:db1c1742","originId":"58058.pyogg0 at https://www.imore.com","fingerprint":"e486917f","content":{"content":"

\n

What you need to know

\n
  • Apple is holding the next Apple Watch Activity Challenge later this month.
  • \n
  • The Health and Sports Day Challenge is taking place on October 14.
  • \n
  • The event will be for Apple Watch users in Japan.
  • \n

The new challenge is taking place in Japan.

\n

Back in August, Apple held a special Apple Watch Activity Challenge in honor of the Grand Canyon National Park's 100th anniversary. Apple just announced the next Activity Challenge is taking place later this month.

\n

Titled "Health and Sports Day Challenge," the new challenge will kick off on October 14 for Apple Watch users in Japan. Here's the description for the challenge.

\n
\n

On October 14, earn this special award by doing any workout for at least 30 minutes. Record your time in the Workout app or any app that adds workouts to Health.

\n
\n

Users will have to complete the work out of their choice for 30 minutes after which they will earn three special stickers for their participation.

\n

Good luck to those Apple Watch users that embark on the Activity Challenge in Japan.

\n

Apple Watch Series 5 review: The best Apple Watch money can buy

\"\"","direction":"ltr"},"title":"Apple is holding a new Apple Watch Activity Challenge on October 14","author":"Danny Zepeda","summary":{"content":"What you need to know\nApple is holding the next Apple Watch Activity Challenge later this month.\nThe Health and Sports Day Challenge is taking place on October 14.\nThe event will be for Apple Watch users in Japan.\nThe new challenge is taking place in Japan.\nBack in August, Apple held a special Apple Watch Activity Challenge in honor of the Grand Canyon National Park's 100th anniversary. Apple just announced the next Activity Challenge is taking place later this month.\nTitled "Health and Sports Day Challenge," the new challenge will kick off on October 14 for Apple Watch users in Japan. Here's the description for the challenge.\nOn October 14, earn this special award by doing any workout for at least 30 minutes. Record your time in the Workout app or any app that adds workouts to Health.\nUsers will have to complete the work out of their choice for 30 minutes after which they will earn three special stickers for their participation.\nGood luck to those Apple Watch users that em...","direction":"ltr"},"alternate":[{"href":"http://feeds.imore.com/~r/TheIphoneBlog/~3/SqzXAQbAtQE/apple-holding-new-apple-watch-activity-challenge-october-14","type":"text/html"}],"canonical":[{"href":"https://www.imore.com/apple-holding-new-apple-watch-activity-challenge-october-14","type":"text/html"}],"crawled":1569957615382,"published":1569957253000,"origin":{"streamId":"feed/http://www.imore.com/rss.xml","title":"iMore - The #1 iPhone, iPad, and iPod touch blog","htmlUrl":"https://www.imore.com/"},"visual":{"url":"http://cdn0.sbnation.com/entry_photo_images/9193021/3462607995_150a6b2624_z_large.jpg","width":630,"height":420,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"tag:daringfireball.net,2019:/linked//6.36093","fingerprint":"f7a11c87","id":"SfSSQbsJpAnFmSl8cDkL3bQGKFOfsp48QsvCmTUjxYM=_16d88b62e1c:50f6b:18991ffa","updated":1569956119000,"author":"John Gruber","alternate":[{"href":"https://medium.com/@adamsandwich/were-sandwich-now-8b6895c5608c","type":"text/html"}],"crawled":1569956703772,"title":"Sandwich","published":1569955917000,"origin":{"streamId":"feed/http://daringfireball.net/feeds/main","htmlUrl":"https://daringfireball.net/","title":"Daring Fireball"},"content":{"direction":"ltr","content":"

Adam Lisagor:

\n
\n

We used to be Sandwich Video. In fact, we’ve been Sandwich Video\nsince 2010, officially. But today, I’m so proud to announce our\nnew name. A shorter name. Leaner, more agile. Why? Just feels\nright. […]

\n

Eventually the ambiguity wore off and Sandwich Video had\nestablished itself as the upstart little production company for\nhot new tech companies to get great bespoke videos. We called them\n“videos” then because what else could they be? Demos? Promos?\nProbably not “commercials” and definitely not “content”. We made\nvideos for clients, and our output had its own built-in\nsubgenre: if you went to Sandwich Video, you ended up with a\nSandwich video. And our style was distinct, so everybody knew it\nwas a Sandwich video. Video video video.

\n
\n

I absolutely love the new Sandwich logo. It’s just perfect. It looks great, it fits the feel of the company to a T, and there’s a timelessness to it. Fun without being goofy or silly is a hard thing to pull off in a logo, but this mark does it. They could be using this logo decades from now and it’ll still look right. The new website is a model of good design and honest copywriting. (Don’t miss Agency Mode.)

\n

See also: Armin Vit on the new logo at Brand New:

\n
\n

I don’t even know why I am over-rationalizing this… it made me\nsmile, it made me happy, and it makes me want a sandwich.

\n
\n"},"visual":{"url":"http://b.vimeocdn.com/ts/451/794/451794956_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/macintosh_initial.json b/Frameworks/Account/AccountTests/Feedly/Initial/macintosh_initial.json new file mode 100644 index 000000000..752c7679f --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/macintosh_initial.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","updated":1569829982898,"continuation":"16d812892b2:4d9ee:18991ffa","items":[{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612860","fingerprint":"dff6ee5d","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4da01:18991ffa","author":"Zac Hall","summary":{"direction":"ltr","content":"
\n

macOS 10.15 includes a gorgeous Catalina-inspired dynamic wallpaper that changes its appearance throughout the day, and recent betas have added even more stunning desktop wallpapers. The upcoming operating system update for Mac includes seven incredible photographs shot around Santa Catalina Island in California. Download each new image below for your wallpaper on any device.

\n

more…

\n

The post Download 7 new beautiful Catalina desktop wallpapers for macOS 10.15 appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/29/macos-catalina-wallpapers/","type":"text/html"}],"crawled":1569829982898,"title":"Download 7 new beautiful Catalina desktop wallpapers for macOS 10.15","published":1569791681000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612847","fingerprint":"3dbdfc28","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4da00:18991ffa","author":"Filipe Espósito","summary":{"direction":"ltr","content":"
\n

Developer and hacker Axi0mX recently shared a new exploit called “checkm8” that offers the possibility to jailbreak almost every Apple A-series CPU up to A11 Bionic chip. Today, the developer has posted a video that shows an iPhone X (which runs on A11 chip) booting up in verbose mode — something that is only possible with more profound modifications to the core of the system.

\n

more…

\n

The post Developer shows a jailbroken iPhone X on iOS 13.1.1 achieved by a new exploit appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/29/iphone-x-jailbreak/","type":"text/html"}],"crawled":1569829982898,"title":"Developer shows a jailbroken iPhone X on iOS 13.1.1 achieved by a new exploit","published":1569787289000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=604933","fingerprint":"3b78fa57","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9ff:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

9to5Mac is brought to you by JustAnswer: Connect 1-on-1 with an Apple support Expert to get step-by-step assistance via phone or online chat, 24/7. Try it now

\n

While password managers have long existed as third-party apps on iOS and macOS, Apple has doubled-down on its own efforts. iCloud Keychain is a feature on macOS and iOS that stores passwords, credit card information, and more.

\n

more…

\n

The post How to use iCloud Keychain to manage and store your passwords appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/29/how-to-use-icloud-keychain-passwords/","type":"text/html"}],"crawled":1569829982898,"title":"How to use iCloud Keychain to manage and store your passwords","published":1569780485000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612838","fingerprint":"c9fdbc24","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9fe:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

Since the release of the first Apple Watch in 2015, Apple has expanded the device’s health capabilities with features like the ECG app and fall detection. In a new interview with The Independent this weekend, Apple’s Jeff Williams, Sumbul Desai, and Kevin Lynch have offered more details on how health became one of the central focuses of Apple Watch.

\n

more…

\n

The post Apple execs explain how Apple Watch became health-focused and what the future could hold appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/29/apple-health-apple-watch-interview/","type":"text/html"}],"crawled":1569829982898,"title":"Apple execs explain how Apple Watch became health-focused and what the future could hold","published":1569773893000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612836","fingerprint":"7772bff","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9fd:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

In this week’s top stories: Apple releases iOS 13.1 and iPadOS to the public, more details on the iPhone 11 and iPhone 11 Pro, speculation on the 2020 iPhones begins, and much more. Read on for all of this week’s biggest news.
\nmore…

\n

The post This week’s top stories: 2020 iPhone rumors, iOS 13.1, ceramic Apple Watch Series 5, more appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/29/top-stories-2020-iphones-ios-13-more/","type":"text/html"}],"crawled":1569829982898,"title":"This week’s top stories: 2020 iPhone rumors, iOS 13.1, ceramic Apple Watch Series 5, more","published":1569770174000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612831","fingerprint":"425534f8","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9fc:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

Fans of augmented reality gaming will soon have yet another option on iOS. Back in May, Microsoft teased that it was working on an AR version of Minecraft dubbed Minecraft Earth for iOS, and now we know the app will begin its rollout as soon as next month.

\n

more…

\n

The post Minecraft Earth AR game to start rolling out next month following demo at WWDC appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/29/minecraft-earth-ios-release/","type":"text/html"}],"crawled":1569829982898,"title":"Minecraft Earth AR game to start rolling out next month following demo at WWDC","published":1569764241000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=611585","fingerprint":"f53aeac9","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9fb:18991ffa","author":"Bradley Chambers","summary":{"direction":"ltr","content":"
\n

Apple Launched Apple News+ during its March event, and to say that it has been underwhelming is beyond an understatement. Despite Apple’s promise of 100s of magazines for a low monthly price, rumors suggest that publishers aren’t happy and I’ve read reports that sign up growth has flatlined. I tried the service for a month, and I came away from it less than impressed. As Apple has continued to deploy its new services across 2019 (Apple Arcade and Apple TV+), I’ve been thinking a lot about News+, the path Apple should take going forward, and what impact it could make in journalism. What should Apple’s strategy be with Apple News+? more…

\n

The post Comment: What should Apple’s strategy be with Apple News+ going forward? appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/29/whats-wrong-with-apple-news-plus/","type":"text/html"}],"crawled":1569829982898,"title":"Comment: What should Apple’s strategy be with Apple News+ going forward?","published":1569762009000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612785","fingerprint":"81865197","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9fa:18991ffa","author":"Jeff Benjamin","summary":{"direction":"ltr","content":"
\n

The iPhone 11 Clear Case is one of only three Apple-made cases for the new baseline iPhone. It joins two of the most basic silicone cases — black and white — in Apple’s lineup of iPhone 11 cases. With that established, pickings are slim if you’re wanting to stick with an Apple-produced case for your new iPhone.

\n

Should you consider dropping $40 for Apple’s iPhone 11 Clear Case, or should you look at more price-friendly and more varied iPhone 11 clear case options from third-party sellers? Watch our brief hands-on video for the details. more…

\n

The post iPhone 11 Clear Case review: is it worth $40? appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/28/iphone-11-clear-case-review-is-it-worth-40-video/","type":"text/html"}],"crawled":1569829982898,"title":"iPhone 11 Clear Case review: is it worth $40?","published":1569705754000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=606966","fingerprint":"ebd6a6be","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f9:18991ffa","author":"Bradley Chambers","summary":{"direction":"ltr","content":"
\n

Apple @ Work is brought to you by Spike, the world’s first conversational email app that helps professionals and teams spend less time on email, and more on getting things done.

\n

I started working in a corporate environment in 2004. Since then, I’ve watched enterprise communications dramatically change. Back then, we relied on Outlook, desk phones, and the occasional cell phone call. Today, the landscape looks completely different. We still have e-mail, but we’ve also added tools like iMessage, Slack, Microsoft Teams, and more. What’s ironic is that I feel overwhelmed at times. I joked with my wife that working in 2019 sometimes feels like keeping inboxes empty. Let’s take a look at the current state of enterprise communication tools. more…

\n

The post Apple @ Work: What’s the state of enterprise communication tools? [Video Webinar] appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/28/enterprise-communication-tools/","type":"text/html"}],"crawled":1569829982898,"title":"Apple @ Work: What’s the state of enterprise communication tools? [Video Webinar]","published":1569701962000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612810","fingerprint":"ea35717d","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f8:18991ffa","author":"Michael Steeber","summary":{"direction":"ltr","content":"
\n

The first weekend of fall began with a fresh harvest of new and expanded Apple Stores. Projects in Mexico City; Fukuoka, Japan; Skokie, Illinois; Columbia, Maryland; and Shanghai, China completed on September 27 and 28, contributing to a grand total of fifteen significant Apple Store openings and expansions for the month.

\n

more…

\n

The post Photos: New Old Orchard and Columbia Apple Stores open appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/28/apple-store-old-orchard-columbia-reopening-photos/","type":"text/html"}],"crawled":1569829982898,"title":"Photos: New Old Orchard and Columbia Apple Stores open","published":1569697833000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612375","fingerprint":"625a49b2","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f7:18991ffa","author":"Sponsored Post","summary":{"direction":"ltr","content":"
\n

Totallee has been known for making thin and minimalistic iPhone cases since 2013. All of its cases are slim and branding-free (!!!). Now, the company has taken this concept and applied it to a luxury leather case. The premium leather case is built to last and offers durable protection while still offering everything you love about totallee’s popular thin cases.

\n

The case is now available for iPhone 11, 11 Pro, 11 Pro Max for only $39. If you like Apple’s leather case, you’re going to love this one, available at $10 less and completely branding-free for a more minimalistic look. more…

\n

The post Totallee launches a premium leather case for $39 for iPhone 11, 11 Pro, and 11 Pro Max appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/28/totallee-leather-case-iphone-11-pro-max/","type":"text/html"}],"crawled":1569829982898,"title":"Totallee launches a premium leather case for $39 for iPhone 11, 11 Pro, and 11 Pro Max","published":1569689543000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612775","fingerprint":"10fd8048","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f6:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

Earlier this week, Apple won a United Nation award for its use of 100 percent renewable energy as well as other climate change efforts. In a new interview with Fast Company, Apple’s VP of environmental, social, and policy initiatives, Lisa Jackson, explained more about how Apple is working with its supply chain partners to help them transition to renewable energy.

\n

more…

\n

The post Lisa Jackson details how Apple is helping transition suppliers to clean energy appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/28/apple-lisa-jackson-clean-energy/","type":"text/html"}],"crawled":1569829982898,"title":"Lisa Jackson details how Apple is helping transition suppliers to clean energy","published":1569687396000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612676","fingerprint":"43c10da0","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f5:18991ffa","author":"Michael Steeber","summary":{"direction":"ltr","content":"
\n

Almost exactly 2 years ago, Apple Vía Santa Fe opened in Mexico City. Apple’s plan for its first store in Mexico was ambitious, with a custom mural to welcome creativity and a private Boardroom to welcome business customers. Now, Apple is ready for something even greater. Apple Antara opened today, bringing Apple’s highest tier of retail design and the best of Today at Apple closer to the heart of Mexico City.

\n

more…

\n

The post Grand opening: Exploring the gardens of Mexico City’s Apple Antara appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/28/apple-store-antara-mexico-opening-photos/","type":"text/html"}],"crawled":1569829982898,"title":"Grand opening: Exploring the gardens of Mexico City’s Apple Antara","published":1569681102000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612755","fingerprint":"991fb9","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f4:18991ffa","author":"Chance Miller","summary":{"direction":"ltr","content":"
\n

Several analysts have already suggested that the iPhone 11 is proving popular in markets such as China. Now, it appears that the colorful iPhone 11 is also a hit in India, which has become an increasingly important market for Apple over the last several years.

\n

more…

\n

The post Analysts: iPhone 11 launch reminiscent of iPhone 6 popularity in India appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/28/iphone-11-india-launch/","type":"text/html"}],"crawled":1569829982898,"title":"Analysts: iPhone 11 launch reminiscent of iPhone 6 popularity in India","published":1569680446000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612738","fingerprint":"cff424e5","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f3:18991ffa","author":"Michael Steeber","summary":{"direction":"ltr","content":"
\n

Japan has been treated to six new and remodeled stores since April 2018 as part of Apple’s significant investment in the country. The latest is Apple Fukuoka, which moved from its original classic location in the Tenjin district to a completely unique new space just 150 meters away. The old store was the last in the world with a vintage feature Apple called The Studio.

\n

more…

\n

The post All-new Apple Fukuoka opens with Japanese shoji screen and bamboo garden appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/27/apple-store-fukuoka-japan-design-photos/","type":"text/html"}],"crawled":1569829982898,"title":"All-new Apple Fukuoka opens with Japanese shoji screen and bamboo garden","published":1569640933000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612692","fingerprint":"a390ef79","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f2:18991ffa","author":"Zac Hall","summary":{"direction":"ltr","content":"
\n

Apple Watch Series 5 introduces the return of the ceramic Edition after being notably absent for the Series 4 lineup. That means this is the first time ceramic has been an option with the redesign that debuted last year, and the new look is stunning. Check out our initial hands-on with the Apple Watch Edition before our full Series 5 review later next week.

\n

more…

\n

The post Apple Watch Edition: Hands-on with the redesigned white ceramic Series 5 appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/27/apple-watch-series-5-edition/","type":"text/html"}],"crawled":1569829982898,"title":"Apple Watch Edition: Hands-on with the redesigned white ceramic Series 5","published":1569628750000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612717","fingerprint":"4175d994","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f1:18991ffa","author":"Filipe Espósito","summary":{"direction":"ltr","content":"
\n

Nintendo’s Mario Kart Tour was released for iOS and Android on Wednesday, and despite all its success, there has been some controversy over its freemium model. That has already been reflected in its availability in some regions. According to the Brussels Times, the company has opted not to launch Mario Kart Tour in Belgium, as loot boxes are prohibited there.

\n

more…

\n

The post Mario Kart Tour is unavailable in Belgium as loot boxes are prohibited there appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/27/mario-kart-tour-is-unavailable-in-belgium-as-loot-boxes-are-prohibited-there/","type":"text/html"}],"crawled":1569829982898,"title":"Mario Kart Tour is unavailable in Belgium as loot boxes are prohibited there","published":1569622083000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612679","fingerprint":"8252c4d4","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9f0:18991ffa","author":"Michael Potuck","summary":{"direction":"ltr","content":"
\n

Apple’s 2019 iPhone lineup includes some fantastic new camera capabilities. However, some features you’re used to using may be harder to find than before. Follow along for how to use camera filters with the iPhone 11 and iPhone 11 Pro.

\n

more…

\n

The post How to use camera filters with the iPhone 11 and iPhone 11 Pro appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/27/how-to-use-camera-filters-iphone-11-pro/","type":"text/html"}],"crawled":1569829982898,"title":"How to use camera filters with the iPhone 11 and iPhone 11 Pro","published":1569616578000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612374","fingerprint":"f1009dfa","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9ef:18991ffa","author":"Sponsored Post","summary":{"direction":"ltr","content":"
\n

This month we’ve teamed up with totallee to give away Apple’s new iPhone 11 Pro Max in midnight green and celebrate the launch of totallee’s cases for the new device. Head below for details on how to enter and for an exclusive 25% off totallee cases.

\n

Get 25% off totallee thin cases for iPhone now w/ code HAPPYHOUR

\n

more…

\n

The post 9to5Rewards: Win iPhone 11 Pro Max + 25% off totallee cases! [Giveaway] appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5mac.com/2019/09/27/rewards-win-iphone-11-pro-max-totallee/","type":"text/html"}],"crawled":1569829982898,"title":"9to5Rewards: Win iPhone 11 Pro Max + 25% off totallee cases! [Giveaway]","published":1569614953000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]},{"keywords":["Apple"],"originId":"https://9to5mac.com/?p=612681","fingerprint":"261d5781","id":"BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16d812892b2:4d9ee:18991ffa","author":"9to5Toys","summary":{"direction":"ltr","content":"
\n

Before Apple’s keynote event this month, there was a lot of anticipation building around whether or not a new USB-C wall charger would ship with the latest iPhones. Well, it came and went, and only select models scored an upgraded wall charger. iPhone 11 remains stuck in the past with a slow 5W USB-A charger, while higher-end Pro models shipped with a faster and modern USB-C power block. That leaves us with the Anker PowerPort III Nano. Described as the “world’s smallest USB-C wall charger” it packs more value and importance than ever before. I’ve been checking it out for a few weeks now following its announcement earlier this month. Head below for some quick thoughts and comparisons with Apple’s own first-party offerings.

\n

more…

\n

The post Hands-on: Anker PowerPort III Nano is the USB-C charger Apple should’ve shipped appeared first on 9to5Mac.

"},"alternate":[{"href":"https://9to5toys.com/2019/09/27/anker-powerport-iii-nano-review/","type":"text/html"}],"crawled":1569829982898,"title":"Hands-on: Anker PowerPort III Nano is the USB-C charger Apple should’ve shipped","published":1569614900000,"origin":{"streamId":"feed/http://9to5mac.com/feed/","htmlUrl":"https://9to5mac.com","title":"9to5Mac"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815","label":"Macintosh"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/mustread_initial.json b/Frameworks/Account/AccountTests/Feedly/Initial/mustread_initial.json new file mode 100644 index 000000000..467346439 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/mustread_initial.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/global.must","items":[]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/newcollection_addcollection.json b/Frameworks/Account/AccountTests/Feedly/Initial/newcollection_addcollection.json new file mode 100644 index 000000000..918b7ef56 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/newcollection_addcollection.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","updated":1569829821629,"items":[{"originId":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","fingerprint":"efab5851","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d996:18991ffa","summary":{"direction":"ltr","content":"

I had the fun of interviewing old friend Daniel Jalkut on the latest episode of The Omni Show.

"},"alternate":[{"href":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","type":"text/html"}],"crawled":1569829821629,"published":1569437386000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","fingerprint":"f53acc86","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d995:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0.1 is almost entirely a bug-fix release — see the release notes for the full scoop.

\n

It includes one sort-of new feature: there’s now a checkbox in Preferences for turning off the unread count in the Dock. (It was a hidden pref — now it’s visible.)

\n

Status

\n

Here’s what else we’re working on:

\n
    \n
  • iOS/iPadOS app
  • \n
  • NetNewsWire 5.0.2 for Mac — which will mainly be about performance (yes, we can make it even faster)
  • \n
  • NetNewsWire 5.1 for Mac — tentative feature list includes content extraction and at least one more syncing option (but we might change our minds on these: anything can happen between now and then)
  • \n
\n

We might also distribute NetNewsWire 5.0.2 for Mac on the Mac App Store. No guarantees yet, of course, but work is happening in that direction. This goes to our goal of getting as many people as possible using RSS readers.

"},"alternate":[{"href":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0.1 Released","published":1568408217000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","fingerprint":"5b6c292f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d994:18991ffa","summary":{"direction":"ltr","content":"

Had to get a new key fob at work today — my old one wore out. Just a couple weeks shy of my fifth anniversary at Omni! Time flies.

\n

I figure I’m just over eight years from retiring, so I’m not even halfway done here. :)

"},"alternate":[{"href":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","type":"text/html"}],"crawled":1569829821629,"published":1568153137000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","fingerprint":"3b5ade1b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d993:18991ffa","summary":{"direction":"ltr","content":"

People have been asking me about supporting iCloud as a sync method for NetNewsWire.

\n

It would be really cool because:

\n
    \n
  • There’s no sign-in
  • \n
  • It’s free — no need to spend money on another service
  • \n
  • It would help broaden the pool of people using RSS, since there would be no additional expense or service they’d need — they could just get going
  • \n
\n

It’s a great idea — no question. Given that my goal is to get as many people as possible using RSS, this makes total sense.

\n

Why we didn’t ship with this feature

\n

For the first release — I still think of it as a 1.0, because it really is — our best bet was to appeal to people already using an existing RSS service. We know that those people like and use RSS, and they’re the people most likely to check out a new RSS app.

\n

(We could have delayed and shipped with support for more existing services, but we figured one was enough to get started with, and we could add other services later. And we are.)

\n

In other words, we tried to make an app that the existing market would like. And that’s the right call when you’re starting out.

\n

Also: iCloud sync makes the most sense when you have both a Mac and an iOS app, and we don’t — the iOS app is still in progress. We totally expect people to use NetNewsWire on the Mac and Unread or Reeder on their iPhone and iPad — and iCloud sync won’t work across apps. This scenario requires using services such as Feedbin.

\n

Why I have no idea when this feature might appear

\n

For any existing RSS service, we can be confident that our effort to support it in NetNewsWire would be successful. This is well-trodden ground: we make some web API calls, integrate with our database, and done. It’s not nothing, but conceptually it’s simple and there’s no cause to worry about technical issues.

\n

But iCloud syncing will mean writing exploratory code and only then finding out if it’s going to work.

\n

Syncing the feeds list should be relatively easy — the real issue is with syncing read/unread/starred states of articles. That means a lot of small records.

\n

Is CloudKit up to this? What are the limits? How fast is it? How reliable?

\n

We just don’t know.

\n

Yes, it’s encouraging that News Explorer has this feature — but that doesn’t tell us much about the limits, reliability, and performance.

\n

Working on this is a risk.

\n

So — as you can imagine — we’re still more keen on supporting existing RSS services, because we know there are plenty of people who for-sure like RSS, and who might like NetNewsWire, but who won’t switch their syncing system just to use NetNewsWire.

\n

That said: I do think we’ll get around to trying this, and I’ll be super-pleased if it works, because it really is a great idea — but we have a bunch of other work to do first. (Including the iOS app!)

"},"alternate":[{"href":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","type":"text/html"}],"crawled":1569829821629,"title":"On Syncing NetNewsWire Using iCloud","published":1567817061000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","fingerprint":"ca200f5a","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d992:18991ffa","summary":{"direction":"ltr","content":"

Markos Charatzas writes about his excitement in joining the Apple developer world in 2009 to his eventual disillusionment today.

"},"alternate":[{"href":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","type":"text/html"}],"crawled":1569829821629,"published":1567788970000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","fingerprint":"66196df9","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d991:18991ffa","summary":{"direction":"ltr","content":"

A number of people have asked that NetNewsWire show the full web page — right there, in the app — after clicking a link.

\n

The idea is pretty good! It solves two big problems:

\n
    \n
  • You get full content, which is great when a feed contains only summaries or truncated articles
  • \n
  • You don’t have to switch to another app: you can stay right where you are
  • \n
\n

You’d think it’s a no-brainer, and we should just go ahead. But there are other considerations.

\n

One big one is that your ad blockers and privacy extensions won’t run. They work in Safari, but they do not extend to other apps that use WebKit. This means that viewing a web page in NetNewsWire would be less secure and more annoying than viewing the same page in Safari (or whatever your browser is).

\n

This points to one of my design principles: the app should have boundaries. Some features belong in the app, and some features are best left to apps that do that feature way better than NetNewsWire could. One of those things is showing web pages — that’s really a web browser feature.

\n

Having boundaries means we can concentrate on doing a great job at the things that do belong in the app.

\n

(Before you mention SFSafariViewController, recall that it’s iOS-only.)

\n

What about the glory days?

\n

“But Brent! In NetNewsWire 2.0 you added a tabbed browser to NetNewsWire, and it was awesome and a hugely popular feature!”

\n

It was! But times have changed. Many websites are hostile these days. In 2005, this feature was fine — but these days it’s totally not.

\n

A winged messenger arrives with a solution

\n

There is a solution to the problem of showing full content and not leaving the app, and it’s a feature that really does belong in an RSS reader: using content extraction to grab the article from the original page.

\n

If you’ve ever used Safari’s Reader view, then you know what I’m talking about. The idea is that NetNewsWire would do something very much like the Reader view (but inline, in the article pane), that grabs the content and formats it nicely, without all the extra junk that is not the article you want to read.

\n

There are a number of open source options for this. We’re looking at using Feedbin’s content extraction service (which wouldn’t require you to have a Feedbin account).

\n

The generous folks at Feedbin are running a copy of the open-source Mercury Parser, and they’ve offered to open this service up to RSS readers like NetNewsWire. (Reeder uses it already, for instance.)

\n

When?

\n

Right now we’re working on NetNewsWire 5.0.1, which is (almost entirely) a bug-fix release. I don’t know what’s going to be in 5.1 yet — we’re still digesting all the feedback, looking at our original roadmap, and thinking about things.

\n

We’re also working on NetNewsWire for iOS! We’re busy.

\n

But this is definitely the kind of feature that should come sooner rather than later.

"},"alternate":[{"href":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"On the Many NetNewsWire Feature Requests to Show Full Web Pages","published":1567661107000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","fingerprint":"bb260103","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d990:18991ffa","summary":{"direction":"ltr","content":"

John Gruber has mentioned, on The Talk Show, that I’ve got some weird ideas about what beta means.

\n

Here are my definitions:

\n

development (d): everything is in progress and the app might be completely unusable.

\n

alpha (a): the app is feature-complete and has no known bugs — but, importantly, it’s had very little testing.

\n

beta (b): the app is feature-complete, has no known bugs, and has been tested — but further testing is still warranted. Every beta is a release candidate.

\n

These are defined in a NetNewsWire Technote. It’s important to have definitions that everybody working on or testing the app understands.

\n

But why these rather strict definitions?

\n

It’s part of our commitment to quality. What matters is the end result — the shipping app — and these definitions make sure we don’t get to beta, or even alpha, with the app up on the table with wires sticking out and pieces missing.

\n

This gives us a big space between development and shipping, and that space is all about making sure the bugs are all fixed.

\n

This is a matter of ethics and pride in our work. Absolutely.

\n

But it’s also pragmatic. This is an open source app, written by volunteers in their spare time, and having this rhythm baked-in to the process helps make sure we can uphold our standards even without full-time developers, managers, and testers.

\n

* * *

\n

And… it bugs me how little real attention our industry pays to quality these days. In some cases the consequences are disastrous; in other cases they’re merely expensive. It doesn’t have to be this way.

\n

If it seems like I’m going too far with my definitions, well, I’m trying to bend the stick here.

"},"alternate":[{"href":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","type":"text/html"}],"crawled":1569829821629,"title":"On My Funny Ideas About What Beta Means","published":1567455823000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","fingerprint":"e526eb19","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98f:18991ffa","summary":{"direction":"ltr","content":"

I love this NetNewsWire write-up on WP Tavern.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","type":"text/html"}],"crawled":1569829821629,"published":1567280353000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","fingerprint":"57ea983b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98e:18991ffa","summary":{"direction":"ltr","content":"

NetNewsWire 5.0 is a 1.0 app in disguise.

\n

And so, as expected, we’ve had a ton of feature requests. Most people tend to request one or two features — and there’s a huge variety in these. People want different things.

\n

Nevertheless, there are a few themes we can pick out from what people are asking for:

\n
    \n
  • More syncing options, especially Feedly support
  • \n
  • iOS app
  • \n
  • Some way to deal with partial-content feeds
  • \n
  • Customization of the article pane (fonts, colors, etc.)
  • \n
  • Traditional view (timeline on top with single lines, article below)
  • \n
  • More sharing options (Instapaper, Pinboard, etc.)
  • \n
  • Customizable keyboard shortcuts
  • \n
  • State restoration
  • \n
  • Localizations
  • \n
  • Hiding read items in the timeline (or dimming them)
  • \n
  • Hiding feeds (in the sidebar) that have no unread articles
  • \n
  • User-created smart feeds
  • \n
\n

The less-common, more singular requests are for things like specific sorting options — there are lots of different small options that people would like.

\n

People have also asked for things that might surprise you (they surprised me) — for instance, we’ve had a request for monochrome icons for the toolbar. Another request for a Dark Mode that’s different from Apple’s Dark Mode. Etc.

\n

How We Choose What To Do Next

\n

The first principle is that we can’t lose what we love about the app. We do our damnedest to ship with no bugs, and the app needs to be fast and, most importantly, it needs to feel lighter-than-air.

\n

Whenever you add things — even if the app remains just as fast, even if there are no bugs — you still run the risk of losing that feeling of lightness. One of the quickest ways to lose that feeling is to add a whole bunch of preferences, View menu options, toolbar commands, and other chrome. So we’re going to be very slow to add things like that.

\n

NetNewsWire needs to not become fiddly. (Earlier versions of NetNewsWire got way too fiddly.)

\n

There are other questions we ask about a feature before we do it.

\n
    \n
  • Will it substantially benefit current users?
  • \n
  • Will it bring a number of new users to the app?
  • \n
  • Does the feature depend on something else being done first?
  • \n
  • How much work will it take?
  • \n
  • Does it require resources (such as new icons) that our programmers can’t provide?
  • \n
  • Does the feature really belong in an RSS reader at all?
  • \n
\n

And, because this is an open source app, there’s another dimension: people. Is someone available? Has someone just shown up who’s eager to work on a specific feature? Those things have an impact on scheduling, too.

\n

The good news is that most of the common feature requests are obvious things to do.

\n

Some examples — not nearly everything, just a few thoughts:

\n

The iOS app is in progress. Maurice Parker has been writing it, and it’s coming along very well. Still plenty more to do, and we won’t ship before iOS 13 ships, but it’s happening.

\n

Adding syncing options is a definite good thing for the app. Doing the first one (Feedbin) was the big effort, because it required building the infrastructure that makes syncing possible. Once that was done, adding additional services is not super-difficult. (Not easy, no. Nothing’s trivial. But at least the infrastructure and patterns are in place.)

\n

We’d like to support all the various services, or at least a majority of them. And we have people working on adding services.

\n

Customization of the article pane will most likely work the way it did in older versions of NetNewsWire: we had theme files which included templates and CSS. The app shipped with a few, and you could make your own and use themes other people made.

\n

This feature shipped with NetNewsWire 2.0, and people really loved it. It was fun!

\n

More sharing options is an obvious good idea. Of course you should be able to send to Instapaper, Pocket, Pinboard, and so on. We shipped with custom support for MarsEdit and the Micro.blog app — mainly because I use those apps. But an RSS reader ought to support as many sharing workflows as possible. That’s one of the core points of the app.

\n

* * *

\n

Anyway — the above doesn’t cover everything. Don’t take any of the above as gospel about what we’re doing or when, or what we’re not doing. We haven’t planned 5.1 yet! It’s too soon.

\n

There are also features that we want to do that people haven’t asked for, but that we think are cool. \uD83C\uDFB8

\n

The take-away from this article should be: we’re being very careful about designing and implementing new features, because we have to make sure NetNewsWire doesn’t lose what makes it special.

\n

But we are doing new features, because there are so many things that can make the app even better — we can make it better for current users and we can bring in new users.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5 Feature Requests","published":1567278518000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/29/follow_through","fingerprint":"444937b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98d:18991ffa","summary":{"direction":"ltr","content":"

Decades ago, when I was working for Dave Winer at UserLand, I learned about the concept of follow-through after a major release.

\n

If you’re an app maker, it might seem like your goal is to get to release day. Get the app done, make it available, publish an announcement, and then get back to coding. Let the world do what it’s going to do.

\n

One bang, and then back to work, in other words.

\n

But that’s not going to maximize your chances for a good release. You need to follow through — you need to keep going.

\n

Some of the things you might do, in no particular order:

\n
    \n
  • Publish tips on using your app — one a day or so
  • \n
  • Update your website with feedback, testimonials, and good reviews
  • \n
  • Be available and communicative about your app
  • \n
  • Go on some podcasts
  • \n
  • Write about how release day went
  • \n
  • Write about plans for the x.0.1 version
  • \n
  • Field bug reports and feature requests gratefully
  • \n
  • Thank reviewers who’ve done a good job
  • \n
  • Make it as easy as possible for reporters and reviewers to get access to your app and to you
  • \n
  • Work to build a community of customers, on Slack or similar
  • \n
\n

I’m sure you can think of more things to do — the above isn’t everything, and every app is different.

\n

But the key is that you don’t just do the release and then stop. Instead, show that you‘re responsive, show that your app has momentum, show that you care enough to keep showing up.

\n

For me, at least, this is the fun part. I realize that’s not true for everybody — but you should do it anyway. \uD83C\uDFA9

"},"alternate":[{"href":"https://inessential.com/2019/08/29/follow_through","type":"text/html"}],"crawled":1569829821629,"title":"Follow-Through","published":1567110304000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","fingerprint":"669e65c4","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98c:18991ffa","summary":{"direction":"ltr","content":"

We have a few reports of a crash where the add-feed-sheet window doesn’t load. There’s a line of code with window! — because of course we expect the window to have been loaded — and it crashes right there.

\n

This crash made zero sense to me, but Daniel Jalkut figured out the most likely cause and was able to reproduce it: it’s because the person has moved the app (from one folder to another) after launching it, while it’s running, and the nib-loading machinery can’t find the nib, because it’s moved along with the app.

\n

Tip: if you’re going to move an app, quit it first, then move it, and then re-launch it!

\n

At any rate: our fix for this will be to load that sheet on startup, and then recycle it on each use. This fix will go into NetNewsWire 5.0.1.

\n

This just fixes the bug with this one nib, though. A more systematic fix — maybe just a warning to the user suggesting they quit and re-launch — would be a good idea.

\n

File under “bugs iOS developers never have to worry about.” \uD83D\uDC07

\n

PS We have a 5.0.1 beta milestone now.

"},"alternate":[{"href":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","type":"text/html"}],"crawled":1569829821629,"title":"Daniel Figures Out One of the Two Crashing Bugs","published":1567022746000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/27/how_release_day_went","fingerprint":"2394a816","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98b:18991ffa","summary":{"direction":"ltr","content":"

Yesterday was a great day! A few things to note, in no particular order:

\n

NetNewsWire got some press coverage, including a well-done review in MacStories.

\n

We got a lot of feature requests, but no bug reports.

\n

Except that we did get a single-digit number of crash logs. On investigation, I found two distinct backtraces — we’ll need to fix those. The thing is, there’s no freakin’ way the app should crash in those spots. Except that, obviously, it can. Rarely, but it happens.

\n

The servers started timing-out at one point during the day. I contacted DreamHost support and they fixed things (and told me that the fixes they applied should prevent this in the future).

\n

There were a number of nice blog posts and tweets about NetNewsWire, which was awesome. After working so hard for so long, it’s great when people appreciate the app. We don’t get paid in money, after all. \uD83D\uDC23

\n

I have no idea how many downloads of the app there were. GitHub is hosting the download, via its releases feature, and I don’t see a way to find out how many times it’s been downloaded. Which is totally fine with me.

\n

* * *

\n

I should say something more about the no-bug-reports. There’s no special magic or talent or anything to this — there’s just the willingness to say that we’re not going to ship until we’ve got the bugs out, and then sticking to that.

\n

This is a matter of pride and ethics, for sure, but there’s another dimension: since the app is open source, it’s written by volunteers (including me), and we have no dedicated support team. Any time we spend fielding bug reports is time taken away from working on the next feature.

\n

Making apps — even, or especially, free apps — is an exercise in economics. With free apps, the economics are even more constrained, because nobody is going to hire even a part-time support person. So we do everything we can do keep costs down — especially time costs.

\n

Plus — buggy apps can be demoralizing to the people who work on them. Part of my job is to make sure people are proud and happy to work on the app. And that means making sure everyone knows we’re super-serious about doing our best to never ship bugs.

"},"alternate":[{"href":"https://inessential.com/2019/08/27/how_release_day_went","type":"text/html"}],"crawled":1569829821629,"title":"How Release Day Went","published":1566937707000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","fingerprint":"175d2cdb","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98a:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0 is shipping!

\n

In case you haven’t been following along until just now: NetNewsWire is an open source RSS reader for Mac. It’s free! You can just download it and use it. No strings.

\n

It’s designed to be stable, fast, and free of bugs. It doesn’t have a lot of features yet, and that’s because we prioritized quality over features. We will be adding more features, of course, but not quickly. We’re also working on an iOS app.

\n

It syncs using Feedbin. We’ll support more systems in the future (as many as possible).

\n

I hope you like it!

\n

Some links…

\n\n

Thanks to so many people

\n

I want to especially thank Sheila Simmons and my family and friends.

\n

This release took five years to make, and for four of those years it wasn’t even called NetNewsWire. It was just a year ago that I got the name NetNewsWire back from Black Pixel — and I thank them again for their wonderful generosity.

\n

I also want to thank Brad Ellis for making the beautiful app icon and toolbar icons. Thanks to our major code contributors: Maurice Parker, Olof Hellman, and Daniel Jalkut. Thanks to Ryan Dotson for writing the Help book. Thanks to Joe Heck for looking after infrastructure issues (especially continuous integration).

\n

Thanks to my co-workers and friends at The Omni Group (which is a wonderful place to work). Thanks to the ever-patient and ever-awesome NetNewsWire beta testers on the Slack group and elsewhere.

\n

And thanks to everyone who’s ever used the app in its 17-years-and-counting run. Because of you, NetNewsWire has been, and remains, the thrill of my career.

"},"alternate":[{"href":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0 Now Available","published":1566834451000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","fingerprint":"e30daaa8","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d989:18991ffa","summary":{"direction":"ltr","content":"

This is a little bit of bad news. It’s not my intention, and it’s not what I want to happen — but NetNewsWire 3.3.2 apparently does not launch in the next version of macOS (10.15, Catalina).

\n

It links to the PubSub framework, which is not included with the next macOS.

\n

NetNewsWire 3.3.2 was the last release of the full version that I worked on, before selling NetNewsWire to Black Pixel, and I’ve heard from lots of people that they’ve been using it ever since. They never switched.

\n

I would rather it continued working forever, but that’s not to be. Not my choice. Sorry about that!

"},"alternate":[{"href":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","type":"text/html"}],"crawled":1569829821629,"title":"End of the Line for NetNewsWire 3.3.2","published":1566515704000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","fingerprint":"c583e740","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d988:18991ffa","summary":{"direction":"ltr","content":"

The NetNewsWire blog has the details on NetNewsWire 5.0b5 — which should be the last beta.

\n

Still planning to do the 5.0 final release Monday morning, which really means doing the release on Sunday and pushing an announcement to this blog Monday morning. :)

\n

The last things on my to-do list are actually writing that announcement and doing screenshots for the NetNewsWire web page. Easy. \uD83D\uDC2F

"},"alternate":[{"href":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","type":"text/html"}],"crawled":1569829821629,"published":1566452581000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/20/immunization","fingerprint":"39a4bdb0","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d987:18991ffa","summary":{"direction":"ltr","content":"

Before every major release I like to try and think of everything mean that people might say about the app. It’s fun!

\n

So we just went through this exercise on the NetNewsWire Slack group. Here’s a taste:

\n
    \n
  • This took five years? I could write an RSS parser in a weekend.
  • \n
  • Can’t get my Twitter and Facebook feeds. Whatever.
  • \n
  • Doesn’t work with my Usenet host.
  • \n
  • The information density of the timeline is… lacking. What the hell.
  • \n
  • Not truly open source since it’s on a Mac.
  • \n
  • Not truly open source since it’s not GPL.
  • \n
  • No vim keys. Why bother.
  • \n
  • Regular people will never use an RSS reader. What’s the point?
  • \n
  • Brent’s last good idea was in 2002. Consider this a textbook case of coasting.
  • \n
  • Great app. Too bad RSS died with Google Reader.
  • \n
  • It totally didn’t pick up my subscriptions from the earlier version. How is this an upgrade?
  • \n
  • When does a 5.0 have fewer features than a 3.0? When it’s NetNewsWire.
  • \n
  • The echo chamber will love this app. They always do.
  • \n
  • Free app. Continues the race to the bottom. Pour one out for Silvio Rizzi.
  • \n
  • No way to send to Instapaper. Fuck it.
  • \n
  • Brent Simmons can’t stop pursuing a technology that even Google famously admitted was not worth bothering with.
  • \n
  • If this app took five years, imagine how long it will take before it will actually sync with Feedly.
  • \n
  • Sure it’s free, but I bet the Feedbin people paid them off, because the only way to sync is to pay money to Feedbin.
  • \n
  • No iCloud sync? Jerks.
  • \n
  • No iOS app. The revolution happened on mobile, Brant. What the actual fuck.
  • \n
  • Shoulda been Catalyst. Dinosaurs wrote this app.
  • \n
  • Not on the Mac App Store? I guess they don’t want users.
  • \n
  • I would totally use this if it had just this one [feature x], which I can’t believe they shipped without. (Multiply this comment by 100, with a different feature x each time.)
  • \n
  • Area Man Can’t Let RSS Go
  • \n
\n

Some feedback will be factually inaccurate, but we like to imagine that too:

\n
    \n
  • I remember using NetNewsWire on OS 9, and it hasn’t really improved since then. They should make it a Cocoa app.
  • \n
  • Doesn’t work with web comics. POS
  • \n
  • Doesn’t support 10.5.
  • \n
  • It should be free.
  • \n
  • You’d think they would have updated the design — but it looks exactly like NetNewsWire of old.
  • \n
  • Why the hell would they build on that aging code base from Black Pixel? I heard it doesn’t even use ARC.
  • \n
  • No way to sync? What’s their actual problem?
  • \n
\n

See? The actual feedback will be nicer than the stuff we thought up. This provides a bit of immunization. :)

\n

But, also, there will be negative feedback we didn’t imagine. That’s the gold!

\n

* * *

\n

Bonus from Daniel Jalkut, but not actually a criticism:

\n
\n

Can’t innovate, my RSS.

\n
"},"alternate":[{"href":"https://inessential.com/2019/08/20/immunization","type":"text/html"}],"crawled":1569829821629,"title":"Immunization","published":1566332363000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]},{"originId":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","fingerprint":"592043f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d986:18991ffa","summary":{"direction":"ltr","content":"

I think we’re still on track for releasing NetNewsWire 5.0 Monday, August 26. There will be one more beta before then.

\n

I’ll be available for podcasts, interviews-via-email, etc. If you’d like to set something up, email me or DM me on Twitter.

"},"alternate":[{"href":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","type":"text/html"}],"crawled":1569829821629,"published":1566259329000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/fc09f383-5a9a-4daa-a575-3efc1733b173","label":"NewCollection"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/programming_initial.json b/Frameworks/Account/AccountTests/Feedly/Initial/programming_initial.json new file mode 100644 index 000000000..f963dfd34 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/programming_initial.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","updated":1569829987669,"continuation":"16d635b7080:1d8b:90d684ff","items":[{"keywords":["Apple","Mac","Xcode"],"originId":"https://indiestack.com/?p=858","fingerprint":"6a684f62","id":"FQCNcmB2/bN37jEqK6evDxyNDr+PE839IP+rONrBVrE=_16d8128a555:4da02:18991ffa","author":"Daniel Jalkut","summary":{"direction":"ltr","content":"With the release of macOS 10.15 fast-approaching, more and more Mac developers will be scurrying to ensure their apps are notarized. This is the process by which binary applications are submitted to Apple for cryptographic seal-of-approval indicating that the app meets minimum requirements for safety, and shows no obvious signs of being malware. Apple offers … Continue reading Notarization Provider IDs "},"alternate":[{"href":"https://indiestack.com/2019/09/notarization-provider-ids/","type":"text/html"}],"crawled":1569829987669,"title":"Notarization Provider IDs","published":1568560761000,"origin":{"streamId":"feed/http://indiestack.com/feed/","htmlUrl":"https://indiestack.com","title":"Indie Stack"},"content":{"direction":"ltr","content":"

With the release of macOS 10.15 fast-approaching, more and more Mac developers will be scurrying to ensure their apps are notarized. This is the process by which binary applications are submitted to Apple for cryptographic seal-of-approval indicating that the app meets minimum requirements for safety, and shows no obvious signs of being malware.

\n

Apple offers substantial documentation about notarizing your apps. Many developers will find that Xcode automatically notarizes the app as part of the built-in process for archiving an app for release. For those of us with existing, automated command-line build & release processes, there is a separate guide just for us:

\n

\nCustomizing the Notarization Workflow

\n

The steps for automating notarization involve running the “altool” command from Terminal. Everything in the guide linked above should work perfectly unless you’re a member of more than one development team. If you have more than one team associated with your Apple ID, the back-end at Apple doesn’t know which one it should notarize on behalf of. You’ll see an error message like this:

\n
\nError: Your Apple ID account is attached to other iTunes providers. You will need to specify which provider you intend to submit content to by using the -itc_provider command. Please contact us if you have questions or need help. (1627)
\n

Here’s where things get fun: what the heck is your ITC provider ID? It’s not listed anywhere obvious on the Apple developer site or in Xcode, and can’t be obtained from the very tool that is asking for it. I came across a message from the ever-helpful Quinn in the Apple Developer Forums. It details a method for locating the provider ID by running a command-line tool, iTMSTransporter, from deep within Apple’s Application Loader app.

\n

Application Loader has since been eliminated from Xcode 11, so if you’re running with modern tools, you’ll be hard pressed to find it. Fear not, the binary is preserved deep within the Xcode app bundle itself:

\n
\n% xcrun -f iTMSTransporter\n/Users/daniel/Applications/Xcode/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter\n
\n

All that said, here is a surefire list of steps for obtaining your ITC Provider ID, or as it’s described in the altool man page, your ASC Provider Shortcode.

\n
    \n
  1. Create a new App-Specific Password from your Apple ID management page.
  2. \n
  3. From Terminal, invoke iTMSTransporter with the following options:\n
    \nxcrun iTMSTransporter -m provider -u <yourAppleID> -p <yourAppSpecificPassword>\n
    \n
  4. \n
  5. At your discretion, revoke the App-Specific Password you created for this process.
  6. \n
\n

NOTE: These instructions apply if you are using Xcode 11. If you’re still using Xcode 10, you’ll need to dig up the iTMSTransporter binary from within Application Loader.app. Instead of “xcrun iTMSTransporter” above, it will be something like /path/to/Application Loader.app/Contents/itms/bin/iTMSTransporter.

\n

If all goes well, you should see a list of your Apple development teams, including the Long Name and Short Name. The Short Name is what you need to pass whenever altool requires an ITC or ASC Provider ID.

"},"visual":{"url":"http://b.vimeocdn.com/ts/442/609/442609877_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Blog"],"originId":"https://www.weheartswift.com/?p=141167","fingerprint":"ddc1f0fc","id":"l+udnvvnAeQSl5COMMeF43kFMnjb8UK43/2J+Rysa8Q=_16d8127c266:4d99c:18991ffa","author":"Florian Marcu","summary":{"direction":"ltr","content":"

It’s been 6 years since Apple launched the first version of Swift programming language. During these years, both the language itself and the ecosystem around it have evolved tremendously. And so did the online resources and options on how to learn Swift. In this article, we are taking a look at a different approach to learning Swift, which is often…

\n

Read more

\n

The post How to Learn Swift Programming Practically appeared first on We ❤ Swift.

"},"alternate":[{"href":"https://www.weheartswift.com/how-to-learn-swift-programming/","type":"text/html"}],"crawled":1569829929574,"title":"How to Learn Swift Programming Practically","published":1568276656000,"origin":{"streamId":"feed/http://www.weheartswift.com/feed/","htmlUrl":"https://www.weheartswift.com","title":"We ❤ Swift"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/fhKs50EFS0SJPjSjfCR7lXwcMfs=/0x0:2040x1360/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59667903/acastro_180508_1777_google_IO_0002.0.jpg","width":1310,"height":873,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d8126f6ee:1c4e:90d684ff","keywords":["Content, Content Strategy"],"originId":"https://alistapart.com/article/the-untapped-power-of-vulnerability-transparency-in-content-strategy/","fingerprint":"173b0f1c","title":"The Untapped Power of Vulnerability & Transparency in Content Strategy","author":"by Travis McKnight","summary":{"content":"\n

In marketing, transparency and vulnerability are unjustly stigmatized. The words conjure illusions of being frightened, imperfect, and powerless. And for companies that shove carefully curated personas in front of users, little is more terrifying than losing control of how people perceive the brand.

\n\n\n

Let’s shatter this illusioned stigma. Authentic vulnerability and transparency are strengths masquerading as weaknesses. And companies too scared to embrace both traits in their content forfeit bona fide user-brand connections for often shallow, misleading engagement tactics that create fleeting relationships.

\n\n\n

Transparency and vulnerability are closely entwined concepts, but each one engages users in a unique way. Transparency is how much information you share, while vulnerability is the truth and meaning behind your actions and words. Combining these ideas is the trick to creating empowering and meaningful content. You can’t tell true stories of vulnerability without transparency, and to be authentically transparent you must be vulnerable.

\n\n\n

To be vulnerable, your brand and its content must be brave, genuine, humble, and open, all of which are traits that promote long-term customer loyalty. And if you’re transparent with users about who you are and about your business practices, you’re courting 94 percent of consumers who say they’re more loyal to brands that offer complete openness and 89 percent of people who say they give transparent companies a second chance after a bad experience.

\n\n\n

For many companies, being completely honest and open with their customers—or employees, in some cases—only happens in a crisis. Unfortunately for those businesses, using vulnerability and transparency only as a crisis management strategy diminishes how sincere they appear and can reduce customer satisfaction.

\n\n\n

Unlocking the potential of being transparent and vulnerable with users isn’t a one-off tactic or quick-fix emergency response tool—it’s a commitment to intimate storytelling that embraces a user’s emotional and psychological needs, which builds a meaningful connection between the storyteller and the audience.

\n\n\n

The three storytelling pillars of vulnerable and transparent content

\n\n\n

In her book, Braving the Wilderness, sociologist Brené Brown explains that vulnerability connects us at an emotional level. She says that when we recognize someone is being vulnerable, we invest in their story and begin to develop an emotional bond. This interwoven connection encourages us to experience the storyteller’s joy and pain, and then creates a sense of community and common purpose among the person being vulnerable and the people who acknowledge that vulnerability.

\n\n\n

Three pillars in a company’s lifecycle embrace this bond and provide an outline for telling stories worthy of a user’s emotional investment. The pillars are:

\n\n\n
  • the origins of a company, product, idea, or situation;
  • intimate narratives about customers’ life experiences;
  • and insights about product success and failure.
\n\n\n

Origin stories

\n\n\n

An origin story spins a transparent tale about how a company, product, service, or idea is created. It is often told by a founder, CEO, or industry innovator. This pillar is usually used as an authentic way to provide crisis management or as a method to change how users feel about a topic, product, or your brand.

\n\n\n

Customers’ life experiences

\n\n\n

While vulnerable origin stories do an excellent job of making users trust your brand, telling a customer’s personal life story is arguably the most effective way to use vulnerability to entwine a brand with someone’s personal identity.

\n\n\n

Unlike an origin story, the customer experiences pillar is focused on being transparent about who your customers are, what they’ve experienced, and how those journeys align with values that matter to your brand. Through this lens, you’ll empower your customers to tell emotional, meaningful stories that make users feel vulnerable in a positive way. In this situation, your brand is often a storytelling platform where users share their story with the brand and fellow customers.

\n\n\n

Product and service insights

\n\n\n

Origin stories make your brand trustworthy in a crisis, and customers’ personal stories help users feel an intimate connection with your brand’s persona and mission. The last pillar, product and service insights, combines the psychological principles that make origin and customer stories successful. The outcome is a vulnerable narrative that rallies users’ excitement about, and emotional investment in, what a company sells or the goals it hopes to achieve.

\n\n\n

Vulnerability, transparency, and the customer journey

\n\n\n

The three storytelling pillars are crucial to embracing transparency and vulnerability in your content strategy because they let you target users at specific points in their journey. By embedding the pillars in each stage of the customer’s journey, you teach users about who you are, what matters to you, and why they should care.

\n\n\n

For our purposes, let’s define the user journey as:

\n\n\n
  • awareness;
  • interest;
  • consideration;
  • conversion;
  • and retention.
\n\n\n

Awareness

\n\n\n

People give each other seven seconds to make a good first impression. We’re not so generous with brands and websites. After discovering your content, users determine if it’s trustworthy within one-tenth of a second.

\n\n\n

Page design and aesthetics are often the determining factors in these split-second choices, but the information users discover after that decision shapes their long-term opinions about your brand. This snap judgement is why transparency and vulnerability are crucial within awareness content.

\n\n\n

When you only get one chance to make a positive first impression with your audience, what content is going to be more memorable?

\n\n\n

Typical marketing “fluff” about how your brand was built on a shared vision and commitment to unyielding customer satisfaction and quality products? Or an upfront, authentic, and honest story about the trials and tribulations you went through to get where you are now?

\n\n\n

Buffer, a social media management company that helped pioneer the radical transparency movement, chose the latter option. The outcome created awareness content that leaves a positive lasting impression of the brand.

\n\n\n

In 2016, Joel Gascoigne, cofounder and CEO of Buffer, used an origin story to discuss the mistakes he and his company made that resulted in laying off 10 employees.

\n\n\n

In the blog post “Tough News: We’ve Made 10 Layoffs. How We Got Here, the Financial Details and How We’re Moving Forward,” Gascoigne wrote about Buffer’s over-aggressive growth choices, lack of accountability, misplaced trust in its financial model, explicit risk appetite, and overenthusiastic hiring. He also discussed what he learned from the experience, the changes Buffer made based on these lessons, the consequences of those changes, and next steps for the brand.

\n\n\n

Gascoigne writes about each subject with radical honesty and authenticity. Throughout the article, he’s personable and relatable; his tone and voice make it obvious he’s more concerned about the lives he’s irrevocably affected than the public image of his company floundering. Because Gascoigne is so transparent and vulnerable in the blog post, it’s easy to become invested in the narrative he’s telling. The result is an article that feels more like a deep, meaningful conversation over coffee instead of a carefully curated, PR-approved response.

\n\n\n

Yes, Buffer used this origin story to confront a PR crisis, but they did so in a way that encouraged users to trust the brand. Buffer chose to show up and be seen when they had no control over the outcome. And because Gascoigne used vulnerability and transparency to share the company’s collective pain, the company reaped positive press coverage and support on social media—further improving brand awareness, user engagement, and customer loyalty.

\n\n\n

However, awareness content isn’t always brand focused. Sometimes, smart awareness content uses storytelling to teach users and shape their worldviews. The 2019 State of Science Index is an excellent example.

\n\n\n

The annual State of Science Index evaluates how the global public perceives science. The 2019 report shows that 87 percent of people acknowledge that science is necessary to solve the world’s problems, but 33 percent are skeptical of science and believe that scientists cause as many problems as they solve. Furthermore, 57 percent of respondents are skeptical of science because of scientists’ conflicting opinions about topics they don’t understand.

\n\n\n

3M, the multinational science conglomerate that publishes the report, says the solution for this anti-science mindset is to promote intimate storytelling among scientists and layfolk.

\n\n\n

3M creates an origin story with its awareness content by focusing on the ins and outs of scientific research. The company is open and straightforward with its data and intentions, eliminating any second guesses users might have about the content they’re digesting.

\n\n\n

The company kicked off this strategy on three fronts, and each storytelling medium interweaves the benefits of vulnerability and transparency by encouraging researchers to tell stories that lead with how their findings benefit humanity. Every story 3M tells focuses on breaking through barriers the average person faces when they encounter science and encouraging scientists to be vulnerable and authentic with how they share their research.

\n\n\n

First, 3M began a podcast series known as Science Champions. In the podcast, 3M Chief Science Advocate Jayshree Seth interviews scientists and educators about the global perception of science and how science and scientists affect our lives. The show is currently in its second season and discusses a range of topics in science, technology, engineering, and math.

\n\n\n

Second, the company worked with science educators, journalist Katie Couric, actor Alan Alda, and former NASA astronaut Scott Kelly to develop the free Scientists as Storytellers Guide. The ebook helps STEM researchers improve how and why they communicate their work with other people—with a special emphasis on being empathetic with non-scientists. The guide breaks down how to develop communications skills, overcome common storytelling challenges, and learn to make science more accessible, understandable, and engaging for others.

\n\n\n

Last, 3M created a film series called Beyond the Beaker that explores the day-to-day lives of 3M scientists. In the short videos, scientists give the viewer a glimpse into their hobbies and home life. The series showcases how scientists have diverse backgrounds, hobbies, goals, and dreams.

\n\n\n

Unlike Buffer, which benefits directly from its awareness content, 3M’s three content mediums are designed to create a long-term strategy that changes how people understand and perceive science, by spreading awareness through third parties. It’s too early to conclude that the strategy will be successful, but it’s off to a good start. Science Champions often tops “best of” podcast lists for science lovers, and the Scientists as Storytellers Guide is a popular resource among public universities.

\n\n\n

Interest

\n\n\n

How do you court new users when word-of-mouth and organic search dominate how people discover new brands? Target their interests.

\n\n\n

Now, you can be like the hundreds of other brands that create a “10 best things” list and hope people stumble onto your content organically and like what they see. Or, you can use content to engage with people who are passionate about your industry and have genuine, open discussions about the topics that matter to you both.

\n\n\n

The latter option is a perfect fit for the product and service insights pillar, and the customers’ life experiences pillar.

\n\n\n

To succeed in these pillars you must balance discussing the users’ passions and how your brand plays into that topic against appearing disingenuous or becoming too self-promotional.

\n\n\n

Nonprofits have an easier time walking this taut line because people are less judgemental when engaging with NGOs, but it’s rare for a for-profit company to achieve this balance. SpaceX and Thinx are among the few brands that are able to walk this tightrope.

\n\n\n

Thinx, a women’s clothing brand that sells period-proof underwear, uses its blog to generate awareness, interest, and consideration content via the customers’ life experiences pillar. The blog, aptly named Periodical, relies on transparency and vulnerability as a cornerstone to engage users about reproductive and mental health.

\n\n\n

Toni Brannagan, Thinx’s content editor, says the brand embraces transparency and vulnerability by sharing diverse ideas and personal experiences from customers and experts alike, not shying away from sensitive subjects and never misleading users about Thinx or the subjects Periodical discusses.

\n\n\n

As a company focused on women’s healthcare, the product Thinx sells is political by nature and entangles the brand with themes of shame, cultural differences, and personal empowerment. Thinx’s strategy is to tackle these subjects head-on by having vulnerable conversations in its branding, social media ads, and Periodical content.

\n\n\n

“Vulnerability and transparency play a role because you can’t share authentic diverse ideas and experiences about those things—shame, cultural differences, and empowerment—without it,” Brannagan says.

\n\n\n

A significant portion of Thinx’s website traffic is organic, which means Periodical’s interest-driven content may be a user’s first touchpoint with the brand.

\n\n\n

“We’ve seen that our most successful organic content is educational, well-researched articles, and also product-focused blogs that answer the questions about our underwear, in a way that’s a little more casual than what’s on our product pages,” Brannagan says. “In contrast, our personal essays and ‘more opinionated’ content performs better on social media and email.”

\n\n\n

Thanks in part to the blog’s authenticity and open discussions about hard-hitting topics, readers who find the brand through organic search drive the most direct conversions.

\n\n\n

Conversations with users interested in the industry or topic your company is involved in don’t always have to come from the company itself. Sometimes a single person can drive authentic, open conversations and create endearing user loyalty and engagement.

\n\n\n

For a company that relies on venture capital investments, NASA funding, and public opinion for its financial future, crossing the line between being too self-promotional and isolating users could spell doom. But SpaceX has never shied away from difficult or vulnerable conversations. Instead, the company’s founder, Elon Musk, embraces engaging with users interests in public forums like Twitter and press conferences.

\n\n\n
\"Twitter
\n\n\n

Musk’s tweets about SpaceX are unwaveringly authentic and transparent. He often tweets about his thoughts, concerns, and the challenges his companies face. Plus, Musk frequently engages with his Twitter followers and provides candid answers to questions many CEOs avoid discussing. This authenticity has earned him a cult-like following.

\n\n\n
\"Elon
\n\n\n

Musk and SpaceX create conversations that target people’s interests and use vulnerability to equally embrace failure and success. Both the company and its founder give the public and investors an unflinching story of space exploration.

\n\n\n

And despite laying off 10 percent of its workforce in January of 2019, SpaceX is flourishing. In May 2019, its valuation had risen to $33.3 billion and reported annual revenue exceeded $2 billion. It also earned global media coverage from launching Musk’s Tesla Roadster into space, recently completed a test flight of its Crew Dragon space vehicle, and cemented multiple new payload contracts.

\n\n\n

By engaging with users on social media and through standard storytelling mediums, Thinx and SpaceX bolster customer loyalty and brand engagement.

\n\n\n

Consideration

\n\n\n

Modern consumers argue that ignorance is not bliss. When users are considering converting with a brand, 86 percent of consumers say transparency is a deciding factor. Transparency remains crucial even after they convert, with 85 percent of users saying they’ll support a transparent brand during a PR crisis.

\n\n\n

Your brand must be open, clear, and honest with users; there is no longer another viable option.

\n\n\n

So how do you remain transparent while trying to sell someone a product? One solution employed by REI and Everlane is to be openly accountable to your brand and your users via the origin stories and product insights pillars.

\n\n\n

REI, a national outdoor equipment retailer, created a stewardship program that behaves as a multifaceted origin story. The program’s content highlights the company’s history and manufacturing policies, and it lets users dive into the nitty-gritty details about its factories, partnerships, product production methods, manufacturing ethics, and carbon footprint.

\n\n\n
\"Screenshot
\n\n\n

REI also employs a classic content hub strategy to let customers find the program and explore its relevant information. From a single landing page, users can easily find the program through the website’s global navigation and then navigate to every tangential topic the program encompasses.

\n\n\n

REI also publishes an annual stewardship report, where users can learn intimate details about how the company makes and spends its money.

\n\n\n
\"Screenshot
\n\n\n

Everlane, a clothing company, is equally transparent about its supply chain. The company promotes an insider’s look into its global factories via product insights stories. These glimpses tell the personal narratives of factory employees and owners, and provide insights into the products manufactured and the materials used. Everlane also published details of how they comply with the California Transparency in Supply Chains Act to guarantee ethical working conditions throughout its supply chain, including refusing to partner with human traffickers.

\n\n\n
\"Screenshot
\n\n\n

The crucial quality that Everlane and REI share is they publicize their transparency and encourage users to explore the shared information. On each website, users can easily find information about the company’s transparency endeavors via the global navigation, social media campaigns, and product pages.

\n\n\n

The consumer response to transparent brands like REI and Everlane is overwhelmingly positive. Customers are willing to pay price premiums for the additional transparency, which gives them comfort by knowing they’re purchasing ethical products.

\n\n\n

REI’s ownership model has further propelled the success of its transparency by using it to create unwavering customer engagement and loyalty. As a co-op where customers can “own” part of the company for a one-time $20 membership fee, REI is beholden to its members, many of which pay close attention to its supply chain and the brands REI partners with.

\n\n\n

After a deadly school shooting in Parkland, Florida, REI members urged the company to refuse to carry CamelBak products because the brand’s parent company manufactures assault-style weapons. Members argued the partnership violated REI’s supply chain ethics. REI listened and halted orders with CamelBak. Members rejoiced and REI earned a significant amount of positive press coverage.

\n\n\n

Conversion

\n\n\n

Imagine you’ve started incorporating transparency throughout your company, and promote the results to users. Your brand also begins engaging users by telling vulnerable, meaningful stories via the three pillars. You’re seeing great engagement metrics and customer feedback from these efforts, but not much else. So, how do you get your newly invested users to convert?

\n\n\n

Provide users with a full-circle experience.

\n\n\n

If you combine the three storytelling pillars with blatant transparency and actively promote your efforts, users often transition from the consideration stage into the conversion state. Best of all, when users convert with a company that already earned their trust on an emotional level, they’re more likely to remain loyal to the brand and emotionally invested in its future.

\n\n\n

The crucial step in combining the three pillars is consistency. Your brand’s stories must always be authentic and your content must always be transparent. The outdoor clothing brand Patagonia is among the most popular and successful companies to maintain this consistency and excel with this strategy.

\n\n\n

Patagonia is arguably the most vocal and aggressive clothing retailer when it comes to environmental stewardship and ethical manufacturing.

\n\n\n

In some cases, the company tells users not to buy its clothing because rampant consumerism harms the environment too much, which they care about more than profits. This level of radical transparency and vulnerability skyrocketed the company’s popularity among environmentally-conscious consumers.

\n\n\n

In 2011, Patagonia took out a full-page Black Friday ad in the New York Times with the headline “Don’t Buy This Jacket.” In the ad, Patagonia talks about the environmental toll manufacturing clothes requires.

\n\n\n

“Consider the R2 Jacket shown, one of our best sellers. To make it required 135 liters of water, enough to meet the daily needs (three glasses a day) of 45 people. Its journey from its origin as 60 percent recycled polyester to our Reno warehouse generated nearly 20 pounds of carbon dioxide, 24 times the weight of the finished product. This jacket left behind, on its way to Reno, two-thirds [of] its weight in waste.”

\n\n\n

The ad encourages users to not buy any new Patagonia clothing if their old, ratty clothes can be repaired. To help, Patagonia launched a supplementary subdomain to its e-commerce website to support its Common Thread Initiative, which eventually got rebranded as the Worn Wear program.

\n\n\n

Patatgonia’s Worn Wear subdomain gets users to engage with the company about causes each party cares about. Through Worn Wear, Patagonia will repair your old gear for free. If you’d rather have new gear, you can instead sell the worn out clothing to Patagonia, and they’ll repair it and then resell the product at a discount. This interaction encourages loyalty and repeat brand-user engagement.

\n\n\n

In addition, the navigation on Patagonia’s main website practically begs users to learn about the brand’s non-profit initiatives and its commitment to ethical manufacturing.

\n\n\n
\"Screenshot
\n\n\n

Today, Patagonia is among the most respected, profitable, and trusted consumer brands in the United States.

\n\n\n

Retention

\n\n\n

Content strategy expands through nearly every aspect of the marketing stack, including ad campaigns, which take a more controlled approach to vulnerability and transparency. To target users in the retention stage and keep them invested in your brand, your goal is to create content using the customers’ life experiences pillar to amplify the emotional bond and brand loyalty that vulnerability creates.

\n\n\n

Always took this approach and ended up with one of its most successful social media campaigns.

\n\n\n
\"An
\n\n\n

In June 2014, Always launched its #LikeAGirl campaign to empower adolescent and teenage girls by transforming the phrase “like a girl” from a slur into a meaningful and positive statement.

\n\n\n

The campaign is centered on a video in which Always tasked children, teenagers, and adults to behave “like a girl” by running, punching, and throwing while mimicking their perception of how a girl performs the activity. Young girls performed the tasks wholeheartedly and with gusto, while boys and adults performed overly feminine and vain characterizations. The director then challenged the person on their portrayal, breaking down what doing things “like a girl” truly means. The video ends with a powerful, heart-swelling statement:

\n\n\n

“If somebody else says that running like a girl, or kicking like a girl, or shooting like a girl is something you shouldn’t be doing, that’s their problem. Because if you’re still scoring, and you’re still getting to the ball in time, and you’re still being first...you’re doing it right. It doesn’t matter what they say.”

\n\n\n

This customer story campaign put the vulnerability girls feel during puberty front and center so the topic would resonate with users and give the brand a powerful, relevant, and purposeful role in this connection, according to an Institute for Public Relations campaign analysis.

\n\n\n

Consequently, the #LikeAGirl campaign was a rousing success and blew past the KPIs Always established. Initially, Always determined an “impactful launch” for the video meant 2 million video views and 250 million media impressions, the analysis states.

\n\n\n

Five years later, the campaign video has more than 66.9 million views and 42,700 comments on YouTube, with more than 85 percent of users reacting positively. Here are a few additional highlights the analysis document points out:

\n\n\n
  • Eighty-one percent of women ages 16–24 support Always in creating a movement to reclaim “like a girl” as a positive and inspiring statement.
  • More than 1 million people shared the video.
  • Thirteen percent of users created user-generated content about the campaign.
  • The #LikeAGirl program achieved 4.5 billion global impressions.
  • The campaign received 290 million social impressions, with 133,000 social mentions, and it caused a 195.3 percent increase in the brand’s Twitter followers.
\n\n\n

Among the reasons the #LikeAGirl content was so successful is that it aligned with Brené Brown’s concept that experiencing vulnerability creates a connection centered on powerful, shared emotions. Always then amplified the campaign’s effectiveness by using those emotions to encourage specific user behavior on social media.

\n\n\n

How do you know if you’re making vulnerable content?

\n\n\n

Designing a vulnerability-focused content strategy campaign begins by determining what kind of story you want to tell, why you want to tell it, why that story matters, and how that story helps you or your users achieve a goal.

\n\n\n

When you’re brainstorming topics, the most important factor is that you need to care about the stories you’re telling. These tales need to be meaningful because if you’re weaving a narrative that isn’t important to you, it shows. And ultimately, why do you expect your users to care about a subject if you don’t?

\n\n\n

Let’s say you’re developing a content campaign for a nonprofit, and you want to use your brand’s emotional identity to connect with users. You have a handful of possible narratives but you’re not sure which one will best unlock the benefits of vulnerability. In a Medium post about telling vulnerable stories, Cayla Vidmar presents a list of seven self-reflective questions that can reveal what narrative to choose and why.

\n\n\n

If you can answer each of Vidmar’s questions, you’re on your way to creating a great story that can connect with users on a level unrivaled by other methods. Here’s what you should ask yourself:

\n\n\n
  • What meaning is there in my story?
  • Can my story help others?
  • How can it help others?
  • Am I willing to struggle and be vulnerable in that struggle (even with strangers)?
  • How has my story shaped my worldview (what has it made me believe)?
  • What good have I learned from my story?
  • If other people discovered this good from their story, would it change their lives?
\n\n\n

While you’re creating narratives within the three pillars, refer back to Vidmar’s list to maintain the proper balance between vulnerability and transparency.

\n\n\n

What’s next?

\n\n\n

You now know that vulnerability and transparency are an endless fountain of strength, not a weakness. Vulnerable content won’t make you or your brand look weak. Your customers won’t flee at the sight of imperfection. Being human and treating your users like humans isn’t a liability.

\n\n\n

It’s time for your brand to embrace its untapped potential. Choose to be vulnerable, have the courage to tell meaningful stories about what matters most to your company and your customers, and overcome the fear of controlling how users will react to your content.

\n\n\n

Origin story

\n\n\n

Every origin story has six chapters:

\n\n\n
  • the discovery of a problem or opportunity;
  • what caused this problem or opportunity;
  • the consequences of this discovery;
  • the solution to these consequences;
  • lessons learned during the process;
  • and next steps.
\n\n\n

Customers’ life experiences

\n\n\n

Every customer journey narrative has six chapters:

\n\n\n
  • plot background to frame the customer’s experiences;
  • the customer’s journey;
  • how the brand plays into that journey (if applicable);
  • how the customer’s experiences changed them;
  • what the customer learned from this journey;
  • and how other people can use this information to improve their lives.
\n\n\n

Product and service insights

\n\n\n

Narratives about product and service insights have seven chapters:

\n\n\n
  • an overview of the product/service;
  • how that product/service affects users;
  • why the product/service is important to the brand’s mission or to users;
  • what about this product/service failed or succeeded;
  • why did that success or failure happen;
  • what lessons did this scenario create;
  • and how are the brand and its users moving forward.
\n\n\n

You have the tools and knowledge necessary to be transparent, create vulnerable content, and succeed. And we need to tell vulnerable stories because sharing our experiences and embracing our common connections matters. So go ahead, put yourself out into the open, and see how your customers respond.

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/MMGA19A7E-c/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/the-untapped-power-of-vulnerability-transparency-in-content-strategy/","type":"text/html"}],"crawled":1569829877486,"published":1569829877486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d8125222e:1c4d:90d684ff","keywords":["Career"],"originId":"https://alistapart.com/article/the-career-management-document/","fingerprint":"687902d8","title":"An Essential Tool for Capturing Your Career Accomplishments","author":"by Jessica Ivins","summary":{"content":"\n

Imagine you’re ready to apply for your next job. Like most busy professionals, you probably haven’t updated your résumé or your portfolio since you looked for your current job. 

\n\n\n

Now you need to update both, and you can’t remember what work you’ve done over the past few years. (In fact, you can barely remember what you’ve done over the past few months!)

\n\n\n

So you scramble to update your résumé with new content. Then you spend all weekend scraping together a new portfolio using screenshots of whatever work evidence you can find on your laptop. You submit the résumé and portfolio with your application, hoping you didn’t forget to include any major career milestones you achieved over the last few years. 

\n\n\n

This is the process most of us use to approach our job search. We wait until we’re ready to find a job, panic at our lack of résumé and portfolio, and pull together a “good enough” version of each for the job application. (Trust me, I’ve done this many times myself.)

\n\n\n

This is a stressful and ineffective way to approach a job search. There’s a much better approach you can take—and you can start working on it now, even if you’re not on the job market.

\n\n\n

The Career Management Document

\n\n\n

A Career Management Document (CMD) is a comprehensive collection of your résumé and portfolio content. It’s a document you update regularly, over time, with all the work you’ve done. 

\n\n\n

When you’re ready to apply for your next job, you’ll have all the résumé and portfolio pieces available in your CMD. All you need to do is assemble those pieces into résumé and portfolio documents, then send the documents off with your job application.

\n\n\n

I update my CMD about once a week. I start by reviewing evidence of my recent work. I review Slack messages, Basecamp posts, emails, and any other current work-related content. I write my accomplishments in the format of résumé bullets, using the framework of responsibilities and accomplishments from this Manager Tools podcast. Then I add those bullets to the CMD. 

\n\n\n

Here are some examples from my CMD:

\n\n\n
  • Coached a student on writing a stronger portfolio story to showcase their advanced UX skills, resulting in the student getting a job interview.
  • Facilitated an end-of-study analysis in under 90 minutes to help the team synthesize user research data from 12 participants.
  • Led a remote retrospective with teams in two offices, developed actionable takeaways, and ended on time despite a delayed start.
\n\n\n

My CMD has several hundred résumé bullets, and it continues to grow. I organize content by year and by project. Within each project are responsibilities and accomplishments.

\n\n\n

I add any content to the CMD that might go into my résumé someday. I include everything I can think of, even if it seems insignificant or trivial at the time. 

\n\n\n

For example, I sometimes help with social media marketing at Center Centre, the UX design school where I’m a faculty member. I include it in my CMD. I don’t plan to pursue social media marketing as a career, but it may be relevant to a future job. Who knows—I may apply to work for an organization that makes social media marketing software someday. In that case, my social media experience could be relevant.

\n\n\n

Include portfolio artifacts with your CMD

\n\n\n

In addition to capturing bullets for my résumé, I capture content for my portfolio. Each week, I gather screenshots of my work, photos of me working with the team, and any other artifacts I can find. I store them in an organized system I can reference later. 

\n\n\n

I also take brief notes about the work I did and store them with the artifacts. That way, if I look back at these materials a year from now, I’ll have notes about what I did during the project, reminding me of the details.

\n\n\n

For example, after I facilitated a user research analysis session late last year, I captured evidence of it for my portfolio. I included photos of the whiteboard where I recorded public notes during the session. I also captured brief notes about who attended the session, the date, and when it took place during the project. 

\n\n\n

You can use whatever tools you’d like to gather evidence of your work. I use Google Docs for the résumé portion of my CMD. I use Dropbox to store my portfolio artifacts. I create Dropbox folders with dates and project names that correspond to the contents of my CMD.

\n\n\n
\"\"

Résumé content from my CMD. I wrote about coaching a student on crafting a presentation for her job interview. The highlighted areas are where I left comments reminding me of the details of the work. Note that some of the résumé bullets seem redundant, which is OK. When I create my next résumé, I’ll choose the most appropriate bullets.
\n\n\n
\"\"

I took notes on a whiteboard while coaching the student. I stored a photo of the whiteboard in Dropbox in a folder named with the date of the work and a description of what I did.
\n\n\n

The key is to collect the evidence regularly and store it in an accessible, organized way that works for you. To know if you’re storing work evidence effectively, ask yourself, “Will I understand this CMD content a year from now based on how I’m capturing and storing it today?” If the answer is “yes,” you’re in good shape.

\n\n\n

Update your CMD regularly

\n\n\n

For the CMD to work when you need it, it needs to be comprehensive and up-to-date. As I mentioned before, I update my CMD once a week. I schedule thirty minutes on my calendar each week so I remember to do it. 

\n\n\n

Sometimes I have a busy week, and I can’t spend thirty minutes on my CMD. So I spend whatever amount of time I have. Some weeks, I only spend ten minutes. Ten minutes per week is better than zero minutes per week. 

\n\n\n

Occasionally, I don’t get a chance to update it because my week is so hectic. That’s OK because I’ll probably get to it the following week. 

\n\n\n

I recommend updating your CMD once a week and not once a month or once a quarter. If you wait even a month, you’ll have trouble remembering what you did three and a half weeks ago. Even worse, if you schedule a CMD update once a month and then miss it, you won’t get to it until the next month. That means you have to think back and remember two months of work, which is hard to do. 

\n\n\n

Updating your CMD every week, while the work is fresh in your mind, gets the best results.

\n\n\n

The CMD benefits you in additional ways

\n\n\n

The CMD can help you prepare for your job search beyond your résumé and your portfolio. 

\n\n\n

You can use it to prepare for a job interview. Since you’re capturing work evidence from each stage of the process in your CMD, you can use that evidence to remember what you did throughout a project. Then, you can craft a story about your role on that project. 

\n\n\n

Hiring managers love to hear stories about your work during job interviews. For instance, if you’re a designer, they want to know the journey you took during your design process, from the start of a project to the end. A detailed CMD will help you remember this process so you can share it in an interview. 

\n\n\n

I’ve even used my CMD to write blog posts. I’ve been blogging regularly for the past two years, and I often refer to my CMD to remember work experience I had that’s relevant to what I’m writing. When I wrote the article “How to Tell Compelling Stories During a UX Job Interview,” I used my CMD to remember interview preparation exercises I did with students. 

\n\n\n

The CMD can also help you track work accomplishments for your quarterly or annual performance reviews. Additionally, you can use it to write job ads when hiring for related roles on your team.

\n\n\n

Lastly, I find it rewarding to peruse my CMD now and then, especially when I look back at work I did over a year ago. The CMD serves as a record of all my professional accomplishments. This record helps me appreciate my professional growth because I see how far my skills have come over time.

\n\n\n

Learn more about the CMD from Manager Tools

\n\n\n

At Center Centre, we originally learned about the Career Management Document through the Manager Tools podcast series.

\n\n\n

Manager Tools’ podcasts explain how to use a CMD for your résumé. We expanded their approach to include portfolio work as well. I recommend listening to their podcasts about creating and maintaining your CMD:

\n\n\n\n\n\n

Prepare for your next job search now

\n\n\n

We tell our students at Center Centre that preparing for your next job search is a process that starts early. It’s like saving for retirement—the sooner you start saving money, the more likely you are to be prepared when the time comes. 

\n\n\n

Similarly, collecting résumé and portfolio content ahead of time will prepare you to find your next job whenever you’re ready to do so. It also prepares you for a sudden job termination like an unexpected layoff. If you lose your job without warning, you’ll likely be under a lot of stress to find a new position. Having a CMD ready will relieve the additional stress of building a résumé and portfolio from scratch. 

\n\n\n

If you don’t have a CMD yet, now is a great time to start one. Schedule 30 minutes this week to begin crafting your repository of work accomplishments. You’ll be glad you did when you seek your next job.

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/R4Jkp4VLCKM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/the-career-management-document/","type":"text/html"}],"crawled":1569829757486,"published":1569829757486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d81234d6e:1c4c:90d684ff","keywords":["Accessibility"],"originId":"https://alistapart.com/article/getting-to-the-heart-of-digital-accessibility/","fingerprint":"5a338ace","title":"Getting to the Heart of Digital Accessibility","author":"by Carie Fisher","summary":{"content":"\n

Quick! Think of the word “developer” or “coder” — what’s the first thing that comes to mind? Maybe a whiteish male in his twenties living in a busy metropolis, wearing a nerdy t-shirt and hoodie? Someone a bit like Mark Zuckerberg? Or maybe a younger Bill Gates or Sergey Brin? Any of the dudes from the HBO series Silicon Valley, perhaps? Certainly no one like me.

\n\n\n

By tech standards, I’m old. I’m also female and a mother. I live in a midwestern town you’ve never heard of and will never visit — a town where the cows vastly outnumber the people. My hair color is (almost) natural and is no longer part of the ROYGBIV collection, so I have no perceived conference street cred. I own about a thousand geeky T-shirts, but never actually wear them in public, opting for more “girly” attire (or so was pointed out by a male colleague). On the surface, I look more suited to taking notes at a PTA meeting than writing code. I’m a bit of an outsider. A tech misfit.

\n\n\n

So when my 11-year-old daughter finished her recent coding camp and excitedly declared, “Now I’m a real developer, Mom, just like you!” there was the usual parent pride, but also a small piece of me that cringed. Because, as much as I support the STEM fields, and want the next generation of girls to be coding wizard-unicorn-ninjas, I really don’t want my own daughter to be a developer. The rationale behind this bold (and maybe controversial) statement comes from a place of protection. The tech world we live in today is far from perfect. I’ve endured my share of misogyny, self-doubt, and sexual harassment. Why wouldn’t I want to protect her from all of that?

\n\n\n

The (diversity) elephant in the (computer) room

\n\n\n

You’ve heard this story before: there is not enough diversity in tech. This puzzling trend seems to continue year after year, even though numerous studies show that by including more people from underrepresented communities, a company can increase its innovation, employee retention, and bottom line. Even with the recent push and supposed support for diversity and inclusivity from many Fortune 500 companies, women and female-identifying people still only hold 20% of all top tech jobs.

\n\n\n

The data from FY 2018 shows that the number of women in technical roles at three of the top tech giants was 24% for Adobe, 26% for Google, and 22% for Facebook. While these numbers show that there is still not enough representation for women, these numbers do reflect a slight increase from the previous year (FY 2017: Adobe 22%, Google 25%, Facebook 15%). But even with this upward trend of hiring women in tech roles, the marginal growth rate has not caught up with the real world. The tech workforce is seriously out of touch with reality if, in 2019, a demographic (women) that represents more than half the global population is still considered a minority.

\n\n\n

Sometimes this lack of diversity at the top level is blamed on a “pipeline” issue. The logic being: “If there are not enough girls who learn to code, then there will not be enough women who can code.” However, programs aimed at teaching girls how to code have skyrocketed in the past few years. Girls now make up about half of the enrollment in high-school coding classes and are scoring almost identically to their male classmates on standardized math and science tests, yet, young women make up only 18% of all Computer Science degrees. I have to wonder if this steep drop in interest has more to do with lack of representation in the tech sphere, than with girls and young women simply not being “smart enough” or “not interested” in working with code? At the very least, the lack of representation certainly doesn’t help.

\n\n\n

Of course, the diversity picture becomes even more abysmal when you consider other underrepresented groups such as people of color, people from the LGBTQ community, and people with disabilities. And while I really don’t like glossing over these deeper diversity issues in tech, because they are abundant and are much more grotesque failings than the male/female ratio, I also don’t feel qualified to speak about these issues. I encourage you to look to and value the voices of others who can speak with higher authority on these deeper diversity issues, such as Ire Aderinokun, Taelur Alexis, Imani Barbarin, Angie Jones, Fatima Khalid, Tatiana Mac, Charlie Owen, Cherry Rae, and so many others. And for those readers who are new to the topic of diversity in tech, watch Tatiana Mac’s recent conference talk How Privilege Defines Performance — it’s well worth the 35 minutes of your life.

\n\n\n

The four stages in the digital accessibility journey

\n\n\n

However you look at it, the numbers don’t lie. There are some pretty significant diversity issues in tech. So how do we fix this issue before the next wave of young developers join the tech workforce? Simple: teach developers to write accessible code.

\n\n\n

This may seem like a joke to some and stretch to others, but hear me out. When we talk about accessible code, what we are really talking about at its core is inclusiveness. The actual process of writing accessible code involves rules and standards, tests and tools; but inclusive development is more abstract than that. It’s a shift in thinking. And when we rethink our approach to development, we go beyond just the base level of simple code functionality. We instead think, how is this code consumed? How can we make it even more intelligible and easier for people to use? Inclusive development means making something valuable, not just accessible, to as many people as we can.

\n\n\n

That line of thinking is a bit abstract, so let’s go through an example. Let’s say you are tasked with updating the color contrast between the text on a webpage or app and the background. What happens at each stage in the accessibility journey?

\n\n\n

Stage 1: Awareness — You are brand new to digital accessibility and are still trying to understand what it is and how you can implement changes in your daily workflow. You may be aware that there is a set of digital accessibility guidelines that other developers follow, but you are a bit hazy on what it all means in a practical sense.

\n\n\n

Stage 2: Knowledge — You know a bit more about digital accessibility and feel comfortable using a few testing tools, so you run an automated accessibility test on your website and it flags a possible issue with the color contrast. Based on your awareness of the guidelines, you know the color contrast ratio between the text and the background needs to be a certain number and that you need a tool to test this.

\n\n\n

Stage 3: Practice — Feeling more confident in your knowledge of digital accessibility rules and best practices, you use a tool to measure the color contrast ratio between the text and the background. Then based on the output of the tool, you modify the hex code to meet the color contrast ratio guidelines and retest to confirm you have met the accessibility requirements for this issue.

\n\n\n

Stage 4: Understanding — You understand that the accessibility guidelines and tools are created with people in mind, and that code is secondary to all of that. One is the means, and the other is the end. In the color contrast example, you understand that people with low-vision or colorblindness need these color contrast changes in order to actually see the words on your web page.

\n\n\n

This is a bit of an oversimplification of the process. But I hope you get the gist — that there are different stages of digital accessibility knowledge and understanding. True beginners may not be to even stage one, but I am finding that group rarer and rarer these days. The word about digital accessibility seems to be out! Which is great; but that’s only the first hurdle. What I’m seeing now is that a lot of people stop at Stage 2: Knowledge or Stage 3: Practice — where you are aware of the digital accessibility guidelines, have some testing tools in your back pocket, and know how to fix some of the issues reported, but haven’t quite connected the dots to the humans they impact.

\n\n\n

From the standpoint of getting daily stuff done, stages two and three are okay stopping points. But what happens when the things you need to do are too complex for a quick fix, or you have no buy-in from your peers or management? I feel that once we get to Stage 4: Understanding, and really get why these kinds of changes are needed, people will be more motivated to make those changes regardless of the challenges involved. When you arrive at stage four, you have gone beyond knowing the basic rules, testing, and coding. You recognize that digital accessibility is not just a “nice to have” but a “must have” and it becomes about quality of life for real people. This is digital inclusion. This is something you can’t unsee, you can’t unlearn, and you can’t ignore.

\n\n\n

Making digital accessibility a priority — not a requirement

\n\n\n

In my role as an accessibility trainer, I like to kick-off each session with the question: “What are you hoping to learn today about digital accessibility?” I ask this question to establish a rapport with the audience and to understand where everyone is in their accessibility journey, but I am also evaluating the level of company and individual buy-in too. There is nothing worse than showing up to teach a group that does not care to be taught. If I hear the words “I am only here because I have to be” — I know it will be an uphill battle to get them anywhere close to Stage 4: Understanding, so I mentally regroup and aim for another stage.

\n\n\n

In my experience, when companies and their leaders say “Digital accessibility is a requirement,” nine times out of ten there is a motivating factor behind this sweeping declaration (for example, impending litigation, or at least the fear of it). When changes are framed as mandatory and packaged as directives from on high with little additional context, people can be resistant and will find excuses to fight or challenge the declaration, and any change can become an uphill battle. Calling something “mandatory” only speaks to Stage 1: Awareness.

\n\n\n

By swapping out one word from the original declaration and saying “Digital accessibility is a priority,” companies and their leaders have reframed the conversation with their employees. When changes are framed as “working towards a solution” and discussed openly and collaboratively, people feel like they are part of the process and are more open to embracing change. In the long run, embracing change becomes part of a company’s culture and leads to innovation (and, yes, inclusion) on all levels. Calling something a priority speaks to Stage 4: Understanding.

\n\n\n

Some of the excuses I often hear from clients for not prioritizing accessibility is that it is too difficult, too costly, and/or too time consuming — but is that really the case? In the same accessibility training, I lead an exercise where we look at a website with an accessibility testing tool and review any issues that came up. With the group’s help we plot out the “impact to user” versus the “remediation effort” on the part of the team. From group to group, while the plots are slightly different, one commonality is that close to 80% of the errors plotted fall into the quadrant of “simple to fix” for the team, but they also fall under “high impact” to the user. Based on this empirical data, I won’t buy the argument from clients who say that accessibility is too difficult and costly and time consuming anymore. It comes down to whether it’s a priority — for each individual and for the company as a whole.

\n\n\n

What will your coding legacy be?

\n\n\n

The infinite monkey theorem states that a monkey hitting keys at random on a typewriter for an infinite amount of time will eventually type any given text, such as the complete works of William Shakespeare. So by that same logic, a programmer hitting keys at random on a computer for an infinite amount of time will almost surely produce a website that is accessible. But where is the thought process? Where is the human element? While all the things we’ve already talked about — awareness, education, and prioritization of accessibility are important steps in making the digital world more inclusive to all — without intent, we are just going to keep randomly tapping away at our computers, repeating the same mistakes over and over again. The intent behind the code has to be part of the process, otherwise accessibility is just another task that has no meaning.

\n\n\n

Maybe I’m naive, but I’d like to think we’ve come to a point in our society where we want our work lives to have meaning. And that we don’t want to just hear about the positive change that is happening, but want to be part of the change. Digital accessibility is a place where this can happen! Not only does understanding and writing purpose-driven code help people with disabilities in the short-run, I believe strongly that is key to solving the overarching diversity issue in tech in the long-run. Developers who reach Stage 4: Understanding, and who prioritize accessible code because they understand it’s fundamentally about people, will also be the ones who help create and cultivate an inclusive environment where people from more diverse backgrounds are also prioritized and accepted in the tech world.

\n\n\n

Because when you strip away all the styles, all the mark-up, all the cool features from a website or app — what’s left? People. And honestly, the more I learn about digital accessibility, the more I realize it’s not about the code at all. Digital accessibility is rooted in the user; and, while I (and countless others) can certainly teach you how to write accessible code, and build you tools, patterns, and libraries to use, I realize we can’t teach you to care. That is a choice you have to make yourself. So think for a moment — what are you leaving the next generation of developers with all that inaccessible code you haven’t given much thought to? Is it the coding legacy you really want to leave? I challenge you to do better for my daughter, her peers, and for the countless others who are not fully represented in the tech community today.

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/GeRjOSU3AQc/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/getting-to-the-heart-of-digital-accessibility/","type":"text/html"}],"crawled":1569829637486,"published":1569829637486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d812178ae:1c4b:90d684ff","keywords":["Application Development, Browsers, JavaScript"],"originId":"https://alistapart.com/article/responsible-javascript-part-2/","fingerprint":"9085fc74","title":"Responsible JavaScript: Part II","author":"by Jeremy Wagner","summary":{"content":"\n

You and the rest of the dev team lobbied enthusiastically for a total re-architecture of the company’s aging website. Your pleas were heard by management—even up to the C-suite—who gave the green light. Elated, you and the team started working with the design, copy, and IA teams. Before long, you were banging out new code.

\n\n\n

It started out innocently enough with an npm install here and an npm install there. Before you knew it, though, you were installing production dependencies like an undergrad doing keg stands without a care for the morning after.

\n\n\n

Then you launched.

\n\n\n

Unlike the aftermath of most copious boozings, the agony didn’t start the morning after. Oh, no. It came months later in the ghastly form of low-grade nausea and headache of product owners and middle management wondering why conversions and revenue were both down since the launch. It then hit a fever pitch when the CTO came back from a weekend at the cabin and wondered why the site loaded so slowly on their phone—if it indeed ever loaded at all.

\n\n\n

Everyone was happy. Now no one is happy. Welcome to your first JavaScript hangover.

\n\n\n

It’s not your fault

\n\n\n

When you’re grappling with a vicious hangover, “I told you so” would be a well-deserved, if fight-provoking, rebuke—assuming you could even fight in so sorry a state.

\n\n\n

When it comes to JavaScript hangovers, there’s plenty of blame to dole out. Pointing fingers is a waste of time, though. The landscape of the web today demands that we iterate faster than our competitors. This kind of pressure means we’re likely to take advantage of any means available to be as productive as possible. That means we’re more likely—but not necessarily doomed—to build apps with more overhead, and possibly use patterns that can hurt performance and accessibility.

\n\n\n

Web development isn't easy. It’s a long slog we rarely get right on the first try. The best part of working on the web, however, is that we don’t have to get it perfect at the start. We can make improvements after the fact, and that’s just what the second installment of this series is here for. Perfection is a long ways off. For now, let’s take the edge off of that JavaScript hangover by improving your site’s, er, scriptuation in the short term.

\n\n\n

Round up the usual suspects

\n\n\n

It might seem rote, but it’s worth going through the list of basic optimizations. It’s not uncommon for large development teams—particularly those that work across many repositories or don’t use optimized boilerplate—to overlook them.

\n\n\n

Shake those trees

\n\n\n

First, make sure your toolchain is configured to perform tree shaking. If tree shaking is new to you, I wrote a guide on it last year you can consult. The short of it is that tree shaking is a process in which unused exports in your codebase don’t get packaged up in your production bundles.

\n\n\n

Tree shaking is available out of the box with modern bundlers such as webpack, Rollup, or Parcel. Grunt or gulp—which are not bundlers, but rather task runners—won’t do this for you. A task runner doesn’t build a dependency graph like a bundler does. Rather, they perform discrete tasks on the files you feed to them with any number of plugins. Task runners can be extended with plugins to use bundlers to process JavaScript. If extending task runners in this way is problematic for you, you’ll likely need to manually audit and remove unused code.

\n\n\n

For tree shaking to be effective, the following must be true:

\n\n\n
  1. Your app logic and the packages you install in your project must be authored as ES6 modules. Tree shaking CommonJS modules isn’t practically possible.
  2. Your bundler must not transform ES6 modules into another module format at build time. If this happens in a toolchain that uses Babel, @babel/preset-env configuration must specify modules: false to prevent ES6 code from being converted to CommonJS.
\n\n\n

On the off chance tree shaking isn’t occurring during your build, getting it to work may help. Of course, its effectiveness varies on a case-by-case basis. It also depends on whether the modules you import introduce side effects, which may influence a bundler’s ability to shake unused exports.

\n\n\n

Split that code

\n\n\n

Chances are good that you’re employing some form of code splitting, but it’s worth re-evaluating how you’re doing it. No matter how you’re splitting code, there are two questions that are always worth asking yourself:

\n\n\n
  1. Are you deduplicating common code between entry points?
  2. Are you lazy loading all the functionality you reasonably can with dynamic import()?
\n\n\n

These are important because reducing redundant code is essential to performance. Lazy loading functionality also improves performance by lowering the initial JavaScript footprint on a given page. On the redundancy front, using an analysis tool such as Bundle Buddy can help you find out if you have a problem.

\n\n\n
\"The
Bundle Buddy can examine your webpack compilation statistics and determine how much code is shared between your bundles.
\n\n\n

Where lazy loading is concerned, it can be a bit difficult to know where to start looking for opportunities. When I look for opportunities in existing projects, I’ll search for user interaction points throughout the codebase, such as click and keyboard events, and similar candidates. Any code that requires a user interaction to run is a potentially good candidate for dynamic import().

\n\n\n

Of course, loading scripts on demand brings the possibility that interactivity could be noticeably delayed, as the script necessary for the interaction must be downloaded first. If data usage is not a concern, consider using the rel=prefetch resource hint to load such scripts at a low priority that won’t contend for bandwidth against critical resources. Support for rel=prefetch is good, but nothing will break if it’s unsupported, as such browsers will ignore markup they doesn’t understand.

\n\n\n

Externalize third-party hosted code

\n\n\n

Ideally, you should self-host as many of your site’s dependencies as possible. If for some reason you must load dependencies from a third party, mark them as externals in your bundler’s configuration. Failing to do so could mean your website’s visitors will download both locally hosted code and the same code from a third party.

\n\n\n

Let’s look at a hypothetical situation where this could hurt you: say that your site loads Lodash from a public CDN. You've also installed Lodash in your project for local development. However, if you fail to mark Lodash as external, your production code will end up loading a third party copy of it in addition to the bundled, locally hosted copy.

\n\n\n

This may seem like common knowledge if you know your way around bundlers, but I’ve seen it get overlooked. It’s worth your time to check twice.

\n\n\n

If you aren’t convinced to self-host your third-party dependencies, then consider adding dns-prefetch, preconnect, or possibly even preload hints for them. Doing so can lower your site’s Time to Interactive and—if JavaScript is critical to rendering content—your site’s Speed Index.

\n\n\n

Smaller alternatives for less overhead

\n\n\n

Userland JavaScript is like an obscenely massive candy store, and we as developers are awed by the sheer amount of open source offerings. Frameworks and libraries allow us to extend our applications to quickly do all sorts of stuff that would otherwise take loads of time and effort.

\n\n\n

While I personally prefer to aggressively minimize the use of client-side frameworks and libraries in my projects, their value is compelling. Yet, we do have a responsibility to be a bit hawkish when it comes to what we install. When we’ve already built and shipped something that depends on a slew of installed code to run, we’ve accepted a baseline cost that only the maintainers of that code can practically address. Right?

\n\n\n

Maybe, but then again, maybe not. It depends on the dependencies used. For instance, React is extremely popular, but Preact is an ultra-small alternative that largely shares the same API and retains compatibility with many React add-ons. Luxon and date-fns are much more compact alternatives to moment.js, which is not exactly tiny.

\n\n\n

Libraries such as Lodash offer many useful methods. Yet, some of them are easily replaceable with native ES6. Lodash’s compact method, for example, is replaceable with the filter array method. Many more can be replaced without much effort, and without the need for pulling in a large utility library.

\n\n\n

Whatever your preferred tools are, the idea is the same: do some research to see if there are smaller alternatives, or if native language features can do the trick. You may be surprised at how little effort it may take you to seriously reduce your app’s overhead.

\n\n\n

Differentially serve your scripts

\n\n\n

There’s a good chance you’re using Babel in your toolchain to transform your ES6 source into code that can run on older browsers. Does this mean we’re doomed to serve giant bundles even to browsers that don’t need them, until the older browsers disappear altogether? Of course not! Differential serving helps us get around this by generating two different builds of your ES6 source:

\n\n\n
  • Bundle one, which contains all the transforms and polyfills required for your site to work on older browsers. You’re probably already serving this bundle right now.
  • Bundle two, which contains little to none of the transforms and polyfills because it targets modern browsers. This is the bundle you’re probably not serving—at least not yet.
\n\n\n

Achieving this is a bit involved. I’ve written a guide on one way you can do it, so there’s no need for a deep dive here. The long and short of it is that you can modify your build configuration to generate an additional but smaller version of your site’s JavaScript code, and serve it only to modern browsers. The best part is that these are savings you can achieve without sacrificing any features or functionality you already offer. Depending on your application code, the savings could be quite significant.

\n\n\n
\"\"
A webpack-bundle-analyzer analysis of a project's legacy bundle (left) versus one for a modern bundle (right). View full-sized image.
\n\n\n

The simplest pattern for serving these bundles to their respective platforms is brief. It also works a treat in modern browsers:

\n\n\n
<!-- Modern browsers load this file: -->\n<script type="module" src="/js/app.mjs"></script>\n<!-- Legacy browsers load this file: -->\n<script defer nomodule src="/js/app.js"></script>
\n\n\n

Unfortunately, there’s a caveat with this pattern: legacy browsers like IE 11—and even relatively modern ones such as Edge versions 15 through 18—will download both bundles. If this is an acceptable trade-off for you, then worry no further.

\n\n\n

On the other hand, you'll need a workaround if you’re concerned about the performance implications of older browsers downloading both sets of bundles. Here’s one potential solution that uses script injection (instead of the script tags above) to avoid double downloads on affected browsers:

\n\n\n
var scriptEl = document.createElement("script");\n\nif ("noModule" in scriptEl) {\n  // Set up modern script\n  scriptEl.src = "/js/app.mjs";\n  scriptEl.type = "module";\n} else {\n  // Set up legacy script\n  scriptEl.src = "/js/app.js";\n  scriptEl.defer = true; // type="module" defers by default, so set it here.\n}\n\n// Inject!\ndocument.body.appendChild(scriptEl);
\n\n\n

This script infers that if a browser supports the nomodule attribute in the script element, it understands type="module". This ensures that legacy browsers only get legacy scripts and modern browsers only get modern ones. Be warned, though, that dynamically injected scripts load asynchronously by default, so set the async attribute to false if dependency order is crucial.

\n\n\n

Transpile less

\n\n\n

I’m not here to trash Babel. It’s indispensable, but lordy, it adds a lot of extra stuff without your ever knowing. It pays to peek under the hood to see what it’s up to. Some minor changes in your coding habits can have a positive impact on what Babel spits out.

\n\n\n
\"\"
https://twitter.com/_developit/status/1110229993999777793
\n\n\n

To wit: default parameters are a very handy ES6 feature you probably already use:

\n\n\n
function logger(message, level = "log") {\n  console[level](message);\n}
\n\n\n

The thing to pay attention to here is the level parameter, which has a default of “log.” This means if we want to invoke console.log with this wrapper function, we don’t need to specify level. Great, right? Except when Babel transforms this function, the output looks like this:

\n\n\n
function logger(message) {\n  var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "log";\n\n  console[level](message);\n}
\n\n\n

This is an example of how, despite our best intentions, developer conveniences can backfire. What was a handful of bytes in our source has now been transformed into much larger in our production code. Uglification can’t do much about it either, as arguments can’t be reduced. Oh, and if you think rest parameters might be a worthy antidote, Babel’s transforms for them are even bulkier:

\n\n\n
// Source\nfunction logger(...args) {\n  const [level, message] = args;\n\n  console[level](message);\n}\n\n// Babel output\nfunction logger() {\n  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n    args[_key] = arguments[_key];\n  }\n\n  const level = args[0],\n        message = args[1];\n  console[level](message);\n}
\n\n\n

Worse yet, Babel transforms this code even for projects with a @babel/preset-env configuration targeting modern browsers, meaning the modern bundles in your differentially served JavaScript will be affected too! You could use loose transforms to soften the blow—and that’s a fine idea, as they’re often quite a bit smaller than their more spec-compliant counterparts—but enabling loose transforms can cause issues if you remove Babel from your build pipeline later on.

\n\n\n

Regardless of whether you decide to enable loose transforms, here’s one way to cut the cruft of transpiled default parameters:

\n\n\n
// Babel won't touch this\nfunction logger(message, level) {\n  console[level || "log"](message);\n}
\n\n\n

Of course, default parameters aren’t the only feature to be wary of. For example, spread syntax gets transformed, as do arrow functions and a whole host of other stuff.

\n\n\n

If you don’t want to avoid these features altogether, you have a couple ways of reducing their impact:

\n\n\n
  1. If you’re authoring a library, consider using @babel/runtime in concert with @babel/plugin-transform-runtime to deduplicate the helper functions Babel puts into your code.
  2. For polyfilled features in apps, you can include them selectively with @babel/polyfill via @babel/preset-env’s useBuiltIns: "usage" option.
\n\n\n

This is solely my opinion, but I believe the best choice is to avoid transpilation altogether in bundles generated for modern browsers. That’s not always possible, especially if you use JSX, which must be transformed for all browsers, or if you’re using bleeding edge language features that aren’t widely supported. In the latter case, it might be worth asking if those features are really necessary to deliver a good user experience (they rarely are). If you arrive at the conclusion that Babel must be a part of your toolchain, then it’s worth peeking under the hood from time to time to catch suboptimal stuff Babel might be doing that you can improve on.

\n\n\n

Improvement is not a race

\n\n\n

As you massage your temples wondering when this horrid JavaScript hangover is going to lift, understand that it’s precisely when we rush to get something out there as fast as we possibly can that the user experience can suffer. As the web development community obsesses on iterating faster in the name of competition, it’s worth your time to slow down a little bit. You’ll find that by doing so, you may not be iterating as fast as your competitors, but your product will be faster than theirs.

\n\n\n

As you take these suggestions and apply them to your codebase, know that progress doesn’t spontaneously happen overnight. Web development is a job. The truly impactful work is done when we’re thoughtful and dedicated to the craft for the long haul. Focus on steady improvements. Measure, test, repeat, and your site’s user experience will improve, and you’ll get faster bit by bit over time.

\n\n\n

Special thanks to Jason Miller for tech editing this piece. Jason is the creator and one of the many maintainers of Preact, a vastly smaller alternative to React with the same API. If you use Preact, please consider supporting Preact through Open Collective.

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/66zevQGQ6YM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/responsible-javascript-part-2/","type":"text/html"}],"crawled":1569829517486,"published":1569829517486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811fa3ee:1c4a:90d684ff","keywords":["Business, Career"],"originId":"https://alistapart.com/article/resilient-management-excerpt/","fingerprint":"4c7dfe28","title":"Resilient Management, An Excerpt","author":"by Lara Hogan","summary":{"content":"\n

In Tuckman’s Stages of Group Development, the Storming stage happens as a group begins to figure out how to work together. Previously, each person had been doing their own thing as individuals, so necessarily a few things need to be ironed out: how to collaborate, how to hit goals, how to determine priorities. Of course there may be some friction here!

\n\n\n

But even if your team doesn’t noticeably demonstrate this kind of internal Storming as they begin to gel, there might be some outside factors at play in your work environment that create friction. During times of team scaling and organizational change—the water we in the web industry are often swimming in—managers are responsible for things like strategy-setting, aligning their team’s work to company objectives, and unblocking the team as they ship their work.

\n\n\n

In addition to these business-context responsibilities, managers need to be able to help their teammates navigate this storm by helping them grow in their roles and support the team’s overall progress. If you and your teammates don’t adapt and evolve in your roles, it’s unlikely that your team will move out of the Storming stage and into the Norming stage of team dynamics.

\n\n\n

To spur this course-correction and growth in your teammates, you’ll end up wearing four different hats:

\n\n\n
  • Mentoring: lending advice and helping to problem solve based on your own experience.
  • Coaching: asking open questions to help your teammate reflect and introspect, rather than sharing your own opinions or quickly problem solving.
  • Sponsoring: finding opportunities for your teammate to level up, take on new leadership roles, and get promoted.
  • Delivering feedback: observing behavior that is or isn’t aligned to what the team needs to be doing and sharing those observations, along with praise or suggestions.
\n\n\n

Let’s dive in to how to choose, and when to use, each of\nthese skills as you grow your teammates, and then talk about\nwhat it looks like when teammates support the overarching\ndirection of the team.\n

\n\n\n

Mentoring

\n\n\n

When I talk to managers, I find that the vast majority have their\nmentor hats on ninety percent of the time when they’re working with their\nteammates. It’s natural!

\n\n\n

In mentoring mode, we’re doling out advice, sharing our perspective,\nand helping someone else problem solve based on that information. Our personal\nexperiences are often what we can talk most confidently about! For this reason,\nmentorship mode can feel really good and effective for the mentor. Having that\nmentor hat on can help the other person overcome a roadblock or know which next\nsteps to take, while avoiding drastic errors that they wouldn’t have seen\ncoming otherwise.

\n\n\n

As a mentor, it’s your responsibility to give advice that’s current and sensitive to the changing dialog happening in our industry. Advice that might work for one person (“Be louder in meetings!” or “Ask your boss for a raise!”) may undermine someone else, because members of underrepresented groups are unconsciously assessed and treated differently. For example, research has shown that “when women are collaborative and communal, they are not perceived as competent—but when they emphasize their competence, they’re seen as cold and unlikable, in a classic ‘double bind’”.

\n\n\n

If you are not a member of a marginalized group, and you have a\nmentee who is, please be a responsible mentor! Try to be aware of the\nway members of underrepresented groups are perceived, and the unconscious bias\nthat might be at play in your mentee’s work environment. When you have your\nmentor hat on, do lots of gut checking to make sure that your advice is going\nto be helpful in practice for your mentee.

\n\n\n

Mentoring is ideal when the mentee is new to their role or to the\norganization; they need to learn the ropes from someone who has firsthand\nexperience. It’s also ideal when your teammate is working on a problem and has\ntried out a few different approaches, but still feels stumped; this is why\npractices like pair coding can help folks learn new things.

\n\n\n

As mentors, we want our mentees to reach beyond us, because our mentees’ success is ultimately our success. Mentorship relationships evolve over time, because each party is growing. Imaginative, innovative ideas often come from people who have never seen a particular challenge before, so if your mentee comes up with a creative solution on their own that you wouldn’t have thought of, be excited for them—don’t just focus on the ways that you’ve done it or seen it done before.

\n\n\n

Managers often default to mentoring mode because it feels like the fastest way to solve a problem, but it falls short in helping your teammate connect their own dots. For that, we’ll look to coaching.

\n\n\n

Coaching

\n\n\n

In mentoring mode, you’re focused on both the problem and the solution. You’ll share what you as the mentor would do or have done in this situation. This means you’re more focused on yourself, and less on the person who is sitting in front of you.

\n\n\n

In coaching mode—an extremely powerful but often underutilized mode—you’re doing two primary things:

\n\n\n
  1. Asking open questions to help the other person explore more of the shape of the topic, rather than staying at the surface level.
  2. Reflecting, which is like holding up a mirror for the other person and describing what you see or hear, or asking them to reflect for themselves.
\n\n\n

These two tools will help you become your teammate’s fiercest champion.

\n\n\n

Open Questions

\n\n\n

“Closed” questions can only be answered with yes or no.\nOpen questions often start with who, what, when, where,\nwhy, and how. But the best open questions are about the\nproblem, not the solution. Questions that start with why tend to make\nthe other person feel judged, and questions that start with how tend\nto go into problem solving mode—both of which we want to avoid while in\ncoaching mode.

\n\n\n

However, what questions can be authentically curious!\nWhen someone comes to you with a challenge, try asking questions like:

\n\n\n
  • What’s most important to you about it?
  • What’s holding you back?
  • What does success look like?
\n\n\n

Let’s say my teammate comes to me and says they’re ready for a\npromotion. Open questions could help this teammate explore what this promotion\nmeans and demonstrate to me what introspection they’ve already done around it.\nRather than telling them what I think is necessary for them to be promoted, I\ncould instead open up this conversation by asking them:

\n\n\n
  • What would you be able to do in the new level\nthat you can’t do in your current one?
  • What skills are required in the new level? What\nare some ways that you’ve honed those skills?
  • Who are the people already at that level that\nyou want to emulate? What about them do you want to emulate?
\n\n\n

Their answers would give me a place to start coaching. These questions might push my teammate to think more deeply about what this promotion means, rather than allowing them to stay surface level and believe that a promotion is about checking off a lot of boxes on a list. Their answers might also open my eyes to things that I hadn’t seen before, like a piece of work that my teammate had accomplished that made a huge impact. But most important, going into coaching mode would start a two-way conversation with this teammate, which would help make an otherwise tricky conversation feel more like a shared exploration.

\n\n\n

Open\nquestions, asked from a place of genuine curiosity, help people feel seen and\nheard. However, if the way you ask your questions comes across as judgy or like\nyou’ve already made some assumptions, then your questions aren’t truly open\n(and your teammate can smell this on you!). Practice your intonation to make\nsure your open questions are actually curious and open.

\n\n\n

By the way, forming lots of open questions (instead of problem solving questions, or giving advice) is tremendously hard for most people. Don’t worry if you don’t get the hang of it at first; it takes a lot of practice and intention over time to default to coaching mode rather than mentoring mode. I promise, it’s worth it.

\n\n\n

Reflections

\n\n\n

Just like open questions, reflections help the other person feel\nseen and heard, and to explore the topic more deeply.

\n\n\n

It’s almost comical how rarely we get the sense that the person\nwe’re talking to is actively listening to us, or focusing entirely on helping\nus connect our own dots. Help your teammates reflect by repeating back to them\nwhat you hear them say, as in:

\n\n\n
  • “What I’m hearing you say is that you’re\nfrustrated with how this project is going. Is that right?”
  • “What I know to be true about you is how deeply\nyou care about your teammates’ feelings.”
\n\n\n

In each of these\nexamples, you are holding up a metaphorical mirror to your teammate, and\nhelping them look into it. You can coach them to reflect, too:

\n\n\n
  • “How does this new architecture project map to\nyour goals?”
  • “Let’s reflect on where you were this time last\nyear and how far you’ve come.”
\n\n\n

Occasionally, you might get a reflection wrong; this gives the\nother person an opportunity to realize something new about their topic, like\nthe words they’re choosing aren’t quite right, or there’s another underlying\nissue that should be explored. So don’t be worried about giving a bad\nreflection; reflecting back what you’re hearing will still help your teammate.

\n\n\n

The act of reflecting can help the other person do a gut check to\nmake sure they’re approaching their topic holistically. Sometimes the act of\nreflection forces (encourages?) the other person to do some really hard work: introspection.\nIntrospection creates an opportunity for them to realize new aspects of the\nproblem, options they can choose from, or deeper meanings that hadn’t occurred\nto them before—which often ends up being a nice shortcut to the right solution.\nOr, even better, the right problem statement.

\n\n\n

When you have your coaching hat on, you don’t need to have all the answers, or even fully understand the problem that your teammate is wrestling with; you’re just there as a mirror and as a question-asker, to help prompt the other person to think deeply and come to some new, interesting conclusions. Frankly, it may not feel all that effective when you’re in coaching mode, but I promise, coaching can generate way more growth for that other person than just giving them advice or sharing your perspective.

\n\n\n

Choose coaching when you’re looking to help someone (especially an emerging leader) hone their strategic thinking skills, grow their leadership aptitude, and craft their own path forward. Coaching mode is all about helping your teammate develop their own brain wrinkles, rather than telling them how you would do something. The introspection and creativity it inspires create deeper and longer-lasting growth.

\n\n\n

Sponsoring

\n\n\n

While you wear the mentoring and coaching hats around your\nteammates, the sponsor hat is more often worn when they’re not around,\nlike when you’re in a 1:1 with your manager, a sprint planning meeting, or\nanother environment where someone’s work might be recognized. You might hear\nabout an upcoming project to acquire a new audience and recommend that a\nbudding user researcher take it on, or you’ll suggest to an All Hands meeting\norganizer that a junior designer should give a talk about a new pattern they’ve\nintroduced to the style guide.

\n\n\n

Sponsorship is all about feeling on the hook for getting\nsomeone to the next level. As someone’s sponsor, you’ll put their name in the\nring for opportunities that will get them the experience and visibility\nnecessary to grow in their role and at the organization. You will put your\npersonal reputation on the line on behalf of the person you’re sponsoring, to\nhelp get them visible and developmental assignments. It’s a powerful tool, and\nthe one most effective at helping someone get to the next level (way more so\nthan mentoring or coaching!).

\n\n\n

The Center for Talent Innovation routinely measures the career benefits of sponsorship (PDF). Their studies have found that when someone has a sponsor, they are way more likely to have access to career-launching work. They’re also more likely to take actions that lead to even more growth and opportunities, like asking their manager for a stretch assignment or a raise.

\n\n\n

When you’re in sponsorship mode, think about the different\nopportunities you have to offer up someone’s name. This might look like:

\n\n\n
  • giving visible/public recognition (company\n“shout outs,” having them present a project demo, thanking them in a launch\nemail, giving someone’s manager feedback about their good work);
  • assigning stretch tasks and projects that are just\nbeyond their current skill set, to help them grow and have supporting\nevidence for a future promotion; or
  • opening the door for them to write blog posts,\ngive company or conference talks, or contribute open-source work.
\n\n\n

Remember that members of underrepresented groups are typically over-mentored, but under-sponsored. These individuals get lots of advice (often unsolicited), coffee outings, and offers to teach them new skills. But it’s much rarer for them to see support that looks like sponsorship.

\n\n\n

This isn’t because sponsors intentionally ignore marginalized folks, but because of in-group bias. Because of how our brains (and social networks) work, the people we’re closest to tend to look mostly like us—and we draw from that same pool when we nominate people for projects, for promotions, and for hires. Until I started learning about bias in the workplace, most of the people I sponsored were white, cisgender women, like myself. Since then, I’ve actively worked to sponsor people of color and nonbinary people. It takes effort and intention to combat our default behaviors—but I know you can do it!

\n\n\n

Take a look at the daily communications you participate in: your work chat logs, the conversations you have with others, the process for figuring out who should fix a bug or work on a new project, and the processes for making your teams’ work visible (like an architecture review, code review, launch calendar, etc.). You’ll be surprised how many moments there are to sponsor someone throughout an average day. Please put in the time and intention to ensure that you’re sponsoring members of underrepresented groups, too.

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/OONUldza8XM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/resilient-management-excerpt/","type":"text/html"}],"crawled":1569829397486,"published":1569829397486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811dcf2e:1c49:90d684ff","keywords":["Design"],"originId":"https://alistapart.com/article/daily-ethical-design/","fingerprint":"c00b5c74","title":"Daily Ethical Design","author":"by Lennart Overkamp","summary":{"content":"Suddenly, I realized that the people next to me might be severely impacted by my work.\nI was having a quick lunch in the airport. A group of flight attendants sat down at the table next to me and started to prepare for their flight. For a while now, our design team had been working on futuristic concepts for the operations control center of these flight attendants’ airline, pushing ourselves to come up with innovative solutions enabled by the newest technologies. As the control center deals with all activities around flying planes, our concepts touched upon everything and everyone within the airline.\nHow was I to know what the impact of my work would be on the lives of these flight attendants? And what about the lives of all the other people working at the airline?\nIdeally, we would have talked to all the types of employees in the company and tested our concepts with them. But, of course, there was no budget (or time) allocated to do so, not to mention we faced the hurdle of convincing (internal) stakeholders of the need.\nNot for the first time, I felt frustrated: practical, real-world constraints prevented me from assessing the impact and quality of my work. They prevented me from properly conducting ethical design.\n

What is ethical design?

\nRight, good question. A very comprehensive definition of ethical design can be found at Encyclopedia.com:\n
\n
Design ethics concerns moral behavior and responsible choices in the practice of design. It guides how designers work with clients, colleagues, and the end users of products, how they conduct the design process, how they determine the features of products, and how they assess the ethical significance or moral worth of the products that result from the activity of designing.
\n
\nIn other words, ethical design is about the “goodness”—in terms of benefit to individuals, society, and the world—of how we collaborate, how we practice our work, and what we create. There’s never a black-and-white answer for whether design is good or bad, yet there are a number of areas for designers to focus on when considering ethics.\n

Usability

\nNowadays usability has conquered a spot as a basic requirement for each interface; unusable products are considered design failures. And rightly so; we have a moral obligation as designers to create products that are intuitive, safe, and free from possibly life-threatening errors. We were all reminded of usability’s importance by last year’s accidental nuclear strike warning in Hawaii. What if, instead of a false-positive, the operator had broadcasted a false-negative?\n

Accessibility

\nLike usability, inclusive design has become a standard item in the requirement list of many designers and companies. (I will never forget that time someone tried to use our website with a screen reader—and got absolutely stuck at the cookie message.) Accessible design benefits all, as it attempts to cover as many needs and capabilities as possible. Yet for each design project, there are still a lot of tricky questions to answer. Who gets to benefit from our solutions? Who is (un)intentionally left out? Who falls outside the “target customer segment”?\n

Privacy

\nAnother day, another Facebook privacy scandal. As we’re progressing into the Data Age, the topic of privacy has become almost synonymous with design ethics. There’s a reason why more and more people use DuckDuckGo as an alternative search engine to Google. Corporations have access to an abundance of personal information about consumers, and as designers we have the privilege—and responsibility—of using this information to shape products and services. We have to consider how much information is strictly necessary and how much people are willing to give up in exchange for services. And how can we make people aware of the potential risks without overloading them?\n

User involvement

\nOverlapping largely with privacy, this focus area is about how we deal with our users and what we do with the data that we collect from them. IDEO has recently published The Little Book of Design Research Ethics, which provides a comprehensive overview of the core principles and guidelines we should follow when conducting design research.\n

Persuasion

\nEthics related to persuasion is about to what extent we may influence the behavior and thoughts of our users. It doesn’t take much to bring acceptable, “white hat” persuasion into gray or even dark territories. Conversion optimization, for example, can easily turn into “How do we squeeze out more revenue from our customers by turning their unconsciousness against them?” Prime examples include Netflix, which convinces us to watch, watch, and watch even more, and Booking.com, which barrages our senses with urgency and social pressure.\n

Focus

\nThe current digital landscape is addictive, distracting, and competing for attention. Designing for focus is about responsibly handling people’s most valuable resource: time. Our challenge is to limit everything that disrupts our users’ attention, lower the addictiveness of products, and create calmness. The Center for Humane Technology has started a useful list of resources for this purpose.\n

Sustainability

\nWhat’s the impact of our work on the world’s environment, resources, and climate? Instead of continuously adding new features in the unrelenting scrum treadmill, how could we design for fewer? We’re in the position to create responsible digital solutions that enable sustainable consumer behavior and prevent overconsumption. For example, apps such as Optimiam and Too Good To Go allow people to order leftover food that would normally be thrashed. Or consider Mutum and Peerby, whose peer-to-peer platforms promote the sharing and reuse of owned products.\n

Society

\nThe Ledger of Harms of the Center for Human Technology is a work-in-progress collection of the negative impacts that digital technology has on society, including topics such as relationships, mental health, and democracy. Designers who are mindful of society consider the impact of their work on the global economy, communities, politics, and health.\n
\n[caption id="attachment_7171650" align="alignnone" width="1200"]\"Focus The focus areas of design ethics. That’s a lot to consider![/caption]\n

Ethics as an inconvenience

\nIdeally, in every design project, we should assess the potential impact in all of the above-mentioned areas and take steps to prevent harm. Yet there are many legitimate, understandable reasons why we often neglect to do so. It’s easy to have moral principles, yet in the real world, with the constraints that our daily life imposes upon us, it’s seldom easy to act according to those principles.\nWe might simply say it’s inconvenient at the moment. That there’s a lack of time or budget to consider all the ethical implications of our work. That there are many more pressing concerns that have priority right now. We might genuinely believe it’s just a small issue, something to consider later, perhaps. Mostly, we are simply unaware of the possible consequences of our work.\nAnd then there’s the sheer complexity of it all: it’s simply too much to simultaneously focus on. When short on time, or in the heat of approaching deadlines and impatient stakeholders, how do you incorporate all of design ethics’ focus areas?\nWhere do you even start?\n

Ethics as a structural practice

\nFor these reasons, I believe we need to elevate design ethics to a more practical level. We need to find ways to make ethics not an afterthought, not something to be considered separately, but rather something that’s so ingrained in our process that not doing it means not doing design at all.\nThe only way to overcome the “inconvenience” of acting ethically is to practice daily ethical design: ethics structurally integrated in our daily work, processes, and tools as designers. No longer will we have to rely on the exceptions among us; those extremely principled who are brave enough to stand up against the system no matter what kind of pressure is put upon them. Because the system will be on our side.\nBy applying ethics daily and structurally in our design process, we’ll be able to identify and neutralize in a very early stage the potential for mistakes and misuse. We’ll increase the quality of our design and our practices simply because we’ll think things through more thoroughly, in a more conscious and structured manner.\nBut perhaps most important is that we’ll establish a new standard for design. A standard that we can sell to our clients as the way design should be done, with ethical design processes and deliverables already included. A standard that can be taught to design students so that the newest generation of designers doesn’t know any better than to apply ethics, always.\n

How to practice daily ethical design?

\nAt this point we’ve arrived at the question of how we can structurally integrate ethics into our design process. How do we make sure that our daily design decisions will result in a product that’s usable and accessible; protects people’s privacy, agency, and focus; and benefits both society and nature?\nI want to share with you some best practices that I’ve identified so far, and how I’ve tried to apply them during a recent project at Mirabeau. The goal of the project was to build a web application that provides a shaver manufacturer’s factory workers insight into the real-time availability of production materials.\n

Connect to your organization’s mission and values

\nBy connecting our designs to the mission and values of the companies we work for, we can structurally use our design skills in a strategic manner, for moral purposes. We can challenge the company to truly live up to its promises and support it in carrying out its mission. This does, however, require you to be aware of the company’s values, and to compare these to your personal values.\nAs I had worked with our example client before, I knew it was a company that takes care of its employees and has a strong focus on creating a better world. During the kick-off phase, we used a strategy pyramid to structure the client’s mission and values, and to agree upon success factors for the project. We translated the company’s customer-facing brand guidelines to employee-focused design principles that maintained the essence of the organization.\n

Keep track of your assumptions

\nThroughout our entire design process, we make assumptions for each decision that we take. By structurally keeping track of these assumptions, you’ll never forget about the limitations of your design and where the potential risks lie in terms of (harmful) impact on users, the project, the company, and society.\nIn our example project, we listed our assumptions about user goals, content, and functionalities for each page of the application. If we were not fully sure about the value for end users, or the accuracy of a user goal, we marked it as a value assumption. When we were unsure if data could be made available, we marked this as a data (feasibility) assumption. If we were not sure whether a feature would add to the manufacturer’s business, we marked it as a scope assumption. Every week, we tested our assumptions with end users and business stakeholders through user tests and sprint demos. Each design iteration led to new questions and assumptions to be tested the next week.\n

Aim to be proven wrong

\nWhile our assumptions are the known unknowns, there are always unknown unknowns that we aren’t aware of but could be a huge risk for the quality and impact of our work. The only way we can identify these is by applying the scientific principle of falsifiability: seeking actively to be proven wrong. Only outsiders can point out to us what we miss as an individual or as a team.\nIn our weekly user tests, we included factory workers and stakeholders with different disciplines, from different departments, and working in different contexts, to identify the edge cases that could break our concept. On one occasion, this made us reconsider the entirety of our concept. Still, we could have done better: although scalability to other factories was an important success factor, we were unable to gather input from those other factories during the project. We felt our only option was to mention this as a risk (“limit to scalability”).\n

Use the power of checklists

\nLet’s face it: we forget things. (Without scrolling up the page, can you name all the focus areas of design ethics?) This is where checklists help us out: they provide knowledge in the world, so that we don’t have to process it in our easily overwhelmed memory. Simple yet powerful, a checklist is an essential tool to practice daily ethical design.\nIn our example project, we used checklists to maintain an overview of questions and assumptions to user test, checking whether we included our design principles properly, and assessing whether we complied to the client’s values, design principles, and the agreed-upon success factors. In hindsight, we could also have taken a moment during the concept phase to go through the list of focus areas for design ethics, as well as have taken a more structural approach to check accessibility guidelines.\n

The main challenge for daily ethical design

\nMost ethics focus areas are quite tangible, where design decisions have immediate, often visible effects. While certainly challenging in their own right, they’re relatively easy to integrate in our daily practice, especially for experienced designers.\nSociety and the environment, however, are more intangible topics; the effects of our work in these areas are distant and uncertain. I’m sure that when Airbnb was first conceived, the founders did not consider the magnitude of its disruptive impact on the housing market. The same goes for Instagram, as its role in creating demand for fast fashion must have been hard to foresee.\nHard, but not impossible. So how do we overcome this challenge and make the impact that we have on society and the environment more immediate, more daily?\n

Conduct Dark Reality sessions

\nThe ancient Greek philosopher Socrates used a series of questions to gradually uncover the invalidity of people’s beliefs. In a very similar way, we can uncover the assumptions and potential disastrous consequences of our concepts in a ‘Dark Reality’ session, a form of speculative design that focuses on stress-testing a concept with challenging questions.\nWe have to ask ourselves—or even better, somebody outside our team has to ask us— questions such as, “What is the lifespan of your product? What if the user base will be in the millions? What are the long-term effects on economy, society, and the environment? Who benefits from your design? Who loses? Who is excluded? And perhaps most importantly, how could your design be misused? (For more of these questions, Alan Cooper provided a great list in his keynote at Interaction 18.)\nThe back-and-forth Q&A of the Dark Reality session will help us consider and identify our concept’s weaknesses and potential consequences. As it is a team effort, it will spark discussion and uncover differences in team members’ ethical values. Moreover, the session will result in a list of questions and assumptions that can be tested with potential users and subject matter experts. In the project for the airline control center, it resulted in more consideration for the human role in automatization and how digital interfaces can continue to support human capabilities (instead of replacing them), and reflection on the role of airports in future society.\nThe dark reality session is best conducted during the convergent parts of the double diamond, as these are the design phases in which we narrow down to realistic ideas. It’s vital to have a questioner from outside the team with strong interviewing skills and who doesn’t easily accept an answer as sufficient. There are helpful tools available to help structure the session, such as the Tarot Cards of Tech and these ethical tools.\n

Take a step back to go forward

\nAs designers, we’re optimists by nature. We see the world as a set of problems that we can solve systematically and creatively if only we try hard enough. We intend well. However, merely having the intention to do good is not going to be enough. Our mindset comes with the pitfall of (dis)missing potential disastrous consequences, especially under pressure of daily constraints. That’s why we need to regularly, systematically take a step back and consider the future impact of our work. My hope is that the practical, structural mindset to ethics introduced in this article will help us agree on a higher standard for design.
\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/72UB8D_Bi2U/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/daily-ethical-design/","type":"text/html"}],"crawled":1569829277486,"published":1569829277486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811bfa6e:1c48:90d684ff","keywords":["Community, Content, Design, Industry & Business, Information Architecture, User Experience"],"originId":"https://alistapart.com/article/trans-inclusive-design/","fingerprint":"27eba67f","title":"Trans-inclusive Design","author":"by Erin White","summary":{"content":"\n

Late one night a few years ago, a panicked professor emailed me: “My transgender student’s legal name is showing on our online discussion board. How can I keep him from being outed to his classmates?” Short story: we couldn’t. The professor created an offline workaround with the student. Years later this problem persists not just in campus systems, but in many systems we use every day.

\n\n\n

To anyone who’d call that an unusual situation, it’s not. We are all already designing for trans users—1 in 250 people in the US identifies as transgender or gender non-binary (based on current estimates), and the number is rising.

\n\n\n

We are web professionals; we can do better than an offline workaround. The choices we make impact the online and offline experiences of real people who are trans, non-binary, or gender-variant—choices that can affirm or exclude, uplift or annoy, help or harm.

\n\n\n

The rest of this article assumes you agree with the concept that trans people are human beings who deserve dignity, respect, and care. If you are seeking a primer on trans-related vocabulary and concepts, please read up and come back later.

\n\n\n

I’m going to cover issues touching on content, images, forms, databases, IA, privacy, and AI—just enough to get you thinking about the decisions you make every day and some specific ideas to get you started.

\n\n\n

“Tried making a Bitmoji again, but I always get disillusioned immediately by their binary gender model from literally step 1 and end up not using it. I don’t feel represented.”

Editorial note: All personal statements quoted in this article have been graciously shared with the express consent of the original authors.
\n\n\n

How we can get things right

\n\n\n

Gender is expansively misconstrued as some interchangeable term for anatomical features. Unlike the constellation of human biological forms (our sex), gender is culturally constructed and varies depending on where you are in the world. It has its own diversity.

\n\n\n

Asking for gender when it is not needed; limiting the gender options users can select; assuming things about users based on gender; or simply excluding folks from our designs are all ways we reify the man-woman gender binary in design decisions.

\n\n\n

Names are fundamentally important

\n\n\n

If we do nothing else, we must get names right. Names are the difference between past and present, invalidation and affirmation, and sometimes safety and danger.

\n\n\n

Yet, many of the systems we use and create don’t offer name flexibility.

\n\n\n

Many programmers and designers have a few misconceptions about names, such as assuming people have one moniker that they go by all the time, despite how common it is for names to change over a lifetime. People might update them after a change in marital status, family situation, or gender, or perhaps someone is known by a nickname, westernized name, or variation on a first name.

\n\n\n

In most locales, legally changing names is extremely difficult, extremely expensive, requires medical documentation, or is completely out of the question.

\n\n\n

Changes to name and gender marker are even more complicated; they tend to be two separate, long-drawn-out processes. To make matters worse, laws vary from state to state within the U.S. and most only recognize two genders—man and woman—rather than allowing non-binary options.Not all trans people change their names, but for those who do, it’s a serious and significant decision that shouldn’t be sabotaged. We can design systems that protect the lives and privacy of our users, respect the fluid nature of personal identity, and act as an electronic curb cut that helps everyone in the process.

\n\n\n

Deadnaming

\n\n\n

One need only search Twitter for “deadname app” to get an idea of how apps can leave users in the lurch. Some of the most alarming examples involve apps and sites that facilitate real-life interactions (which already involve a measure of risk for everyone).

\n\n\n

“Lyft made it completely impossible for me to change my name on its app even when it was legally changed. I reached out to their support multiple times and attempted to delete the account and start over with no result. I was completely dependent on this service for groceries, appointments, and work, and was emotionally exhausted every single time I needed a ride. I ended up redownloading Uber - even though there was a strike against the service - which I felt awful doing. But Uber allowed me to change my name without any hoops to jump through, so for the sake of my mental health, I had to.”

\n\n\n

Trans people are more likely to experience financial hardship, so using payment apps to ask for donations is often necessary. Some of these services may reveal private information as a matter of course, leaving them exposed and potentially at risk.

\n\n\n

There are also ramifications when linked services rely on our data sources for name information, instigating an unpredictable cascade effect with little or no recourse to prevent the sharing of sensitive details.

\n\n\n

These are examples of deadnaming. Deadnaming is what happens when someone’s previous or birth name is used, rather than the name the person uses now. Deadnaming is invalidating at the least, even as a faux pas, but can be psychologically devastating at the other extreme, even putting lives at risk.The experiences of trans, non-binary, or gender-variant folk can vary widely, and they live in disparate conditions throughout the world. Many are thriving and creating new and joyful ways to resist and undo gender norms, despite the common negative narrative around trans lives. Others can face hardship; trans people are more likely to be unstably housed, underemployed, underpaid, and targets of violence in and out of their homes, workplaces, and intimate relationships. The ramifications are amplified for people of color and those with disabilities, as well as those in precarious living/working situations and environments where exposure can put them in harm’s way.

\n\n\n

Design for name changes

\n\n\n

Heres what we can do:

\n\n\n

Design for renaming. Emma Humphries’ talk on renaming covers this nicely. Airbnb has developed policies and procedures for users who’ve transitioned, allowing users to keep their review histories intact with amended names and/or pronouns.

\n\n\n

Get rid of “real name” requirements. Allow people to use names they go by rather than their legal first names.

\n\n\n

Clarify when you actually need a legal name, and only use that in conjunction with a display name field.

\n\n\n

Have a name change process that allows users to change their names without legal documentation. (It’s likely that you have procedures for marriage-related name changes already.)

\n\n\n

Ensure users can still change their display names when connecting with other data sources to populate users’ names.

\n\n\n

Don’t place onerous restrictions on changes. Once someone creates a username, web address, or profile URL, allow them to change it.

\n\n\n

Draft a code of conduct if you’re part of an online community, and make sure to include policies around deadnaming. Twitter banned deadnaming last year.

\n\n\n

Allow people to be forgotten. When people delete their accounts for whatever reason, help them make sure that their data is not lingering in your systems or in other places online.

\n\n\n

Update the systems users don’t see, too

\n\n\n

Identity management systems can be a mess, and name changes can reveal the failures among those systems, including hidden systems that users don’t see.

\n\n\n

One Twitter user’s health insurance company kept their ID number between jobs but changed their gender. Another user updated their display name but got an email confirmation addressed to their legal name.

\n\n\n

Hidden information can also undermine job opportunities:

\n\n\n

“At a university as a student, I transitioned and changed my name and gender to be a woman. TWELVE YEARS later after being hired to work in the Libraries, the Libraries HR coordinator emailed me that I was listed as male still in the database. He changed it on my asking, but I have to wonder how long… was it a factor in my being turned down for jobs I applied to… who had seen that..?”

\n\n\n

Emma Humphries details the hidden systems that can carry out-of-date information about users. Her tips for database design include:

\n\n\n
  • Don’t use emails as unique IDs.
  • Use an invariant user ID internally, and link the user’s current email and display name to it.
\n\n\n

Images

\n\n\n

Visuals should allow room for representation and imagination rather than a narrow subset of the usual suspects: figures who appear to be straight, cisgender, able-bodied, and white/Caucasian.

\n\n\n

What we can do is feature a variety of gender presentations, as well as not assume someone’s gender identity if they buy certain items.

\n\n\n

Some companies, like Wildfang and Thinx, offer a broad array of images representing different races, body sizes, and gender expressions on their websites and in their ads.

\n\n\n

Many are also choosing not to hire models, allowing room for imagination and versatility:

\n\n\n

“I got a catalog for a ‘classic menswear company’ that features zero photos of any person of any gender. Now if only I could afford an $800 blazer...”

\n\n\n

Here's what we can do:

\n\n\n

Actively recruit diverse groups of models for photos. And pay them!

\n\n\n

If you can’t shoot your own photos, Broadly has recently launched a trans-inclusive stock photo collection free for wide use. Avataaars allows users to create an avatar without selecting a gender.

\n\n\n

Information architecture

\n\n\n

How we organize information is a political act and a non-neutral decision (librarians have said this for a while). This applies to gender-based classifications.

\n\n\n

Many companies that sell consumer goods incorporate gender into their product design and marketing, no matter what. The product itself might be inherently gender-neutral (such as clothing, toys, bikes, or even wine), but these design and marketing decisions can directly impact the information architecture of websites.

\n\n\n

Here's what we can do:

\n\n\n

Evaluate why any menus, categories, or tags are based on gender, and how it can be done differently:

\n\n\n

“Nike has a ‘gender neutral’ clothing category, yet it’s listed under ‘men’ and ‘women’ in the website architecture. \uD83E\uDD14”

\n\n\n

Forms

\n\n\n

Forms, surveys, and other types of data gathering are surefire ways to include or exclude people. If you ask for information you don’t need or limit the options that people can select, you risk losing them as users.

\n\n\n

Here's what we can do:

\n\n\n

Critically evaluate why you are asking for personal information, including gender. Will that information be used to help someone, or sell things to your advertisers?

\n\n\n

"Why does the @CocaCola site make me select a gender just to make a purchase? Guess my family isn't getting personalized Coke bottles for Christmas."

\n\n\n

If you are asking users for their gender, you’d better have a good reason and options that include everyone. A gender field should have more than two options, or should ask for pronouns instead. When including more than binary options, actually record the selections in your databases instead of reclassifying answers as male/female/null, otherwise you risk losing trust when disingenuous design decisions become public.

\n\n\n

Honorifics are infrequently used these days, but it takes little work to add gender-inclusive titles to a list. For English-language sites, “Mx.” can go alongside “Mr.” and “Ms.” without fuss. United Airlines debuted this option earlier this year.

\n\n\n

Content

\n\n\n

Here's what we can do:

\n\n\n

Avoid inappropriately gendered language. Your style guide should include singular “they” instead of “he/she” or “s/he,” and exclude frequently used words and phrases that exclude trans folks. Resources such as this transgender style guide are a quick way to check your language and benchmark your own content guidelines.

\n\n\n

Check assumptions about gender and biology. Not everyone who can have a period, can get pregnant, or can breastfeed identifies as women or mothers—just as not everyone who identifies as women or mothers can have periods, can get pregnant, or can breastfeed. Thinx, a company that sells period underwear, has an inclusive tagline: “For people with periods.”

\n\n\n

Avoid reinforcing the binary. Groups of people aren’t “ladies and gentlemen” or “boys and girls.” They are folks, people, colleagues, “y’all,” or even “all y’all.”

\n\n\n

Pronouns aren’t “preferred”—they’re just pronouns. Calling pronouns preferred suggests that they’re optional and are replacing a “true” pronoun.

\n\n\n

Avoid reinforcing stereotypes about trans people. Not all trans people are interested in medically transitioning, or in “passing.” They also aren’t fragile or in need of a savior. Gender is separate from sexual orientation. You can’t “tell” someone is trans.

\n\n\n

Privacy, surveillance, and nefarious AI

\n\n\n

We’ve heard the story of algorithms identifying a pregnant teen before her parents knew. What if an algorithm predicts or reveals information about your gender identity?

\n\n\n

Inferences. Users’ genders are assumed based on their purchase/browsing history.

\n\n\n

Recommendations. A user bought something before they transitioned and it shows up in “recommended because you bought X.”

\n\n\n

Predictions. Users’ genders are not only inferred but used to predict something else based on characteristics of that gender. Even if you don’t tell big websites what your gender is, they assume one for you based on your interests. That kind of reductive essentialism can harm people of all genders. One of this article’s peer readers summed this up:

\n\n\n

“Gender markers are a poor proxy for tastes. I like dresses, cute flats, and Raspberry Pis.”

\n\n\n

Flashbacks. “On this day” algorithms remind users of the past, sometimes for better (“I’ve come so far”) or for worse (“don’t remind me”).

\n\n\n

AI-based discrimination

\n\n\n

AI and surveillance software can also reinforce norms about what men’s and women’s bodies should look like, resulting in harrowing airline travel experiences and creating AI-based discrimination for trans people.

\n\n\n

So, too, can trans folks’ public data be used for projects that they don’t consent to. Just because we can use AI for something—like determining gender based on a face scan—doesn’t mean we should.

\n\n\n

Here's what we can do:

\n\n\n

Read up and proactively mitigate bias. AI and algorithms can reflect developers’ biases and perpetuate stereotypes about how people’s bodies should look. Use AI to challenge the gender binary rather than reinforce it. Design for privacy first. Hire more types of people who represent different lived experiences.

\n\n\n

Toward a gender-inclusive web

\n\n\n

The ideas I’ve offered here are only starting points. How you choose to create space for trans folks is going to be up to you. I don’t have all the solutions here, and there is no singular trans experience. Also, language, definitions, and concepts change rapidly.

\n\n\n

We shouldn’t use any of these facts as excuses to keep us from trying.

\n\n\n

When we start to think about design impact on trans folks, the ideas we bring into question can benefit everyone. Our designs should go beyond including—they should affirm and validate. Ideally, they will also reflect organizational cultures that support diversity and inclusion.

\n\n\n

Here's what we can do:

\n\n\n

Keep learning. Learn how to be a good ally. Pay trans user research participants to help validate your design assumptions. Hire trans people on your team and don't hang them out to dry or make them do all the hard work around inclusion and equity. Make it everyone’s job to build a more just web and world for everybody.

\n\n\n

Editorial note: All personal statements quoted in this article have been graciously shared with the express consent of the original authors.

\n\n\n

This article is stronger and wiser thanks to Mica McPheeters at A List Apart and the following peer readers. Thank you.

\n\n\n

Jake Atchison
Katherine Deibel, Ph.D.
Justina F. Hall
Austyn Higgs
Emma Humphries
Tara Robertson
Levi R. Walter

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/h3VJ6VzV3vU/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/trans-inclusive-design/","type":"text/html"}],"crawled":1569829157486,"published":1569829157486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811a25ae:1c47:90d684ff","keywords":["Information Architecture"],"originId":"https://alistapart.com/article/everyday-information-architecture-excerpt/","fingerprint":"e72e3567","title":"Everyday Information Architecture: Auditing for Structure","author":"by Lisa Maria Martin","summary":{"content":"\n

Just as we need to\nunderstand our content before we can recategorize it, we need to understand the\nsystem before we try to rebuild it.

\n\n\n

Enter the structural audit: a review of the site focused solely on its menus, links, flows, and hierarchies. I know you thought we were done with audits back in Chapter 2, but hear me out! Structural audits have an important and singular purpose: to help us build a new sitemap.

\n\n\n

This isn’t about recreating the intended sitemap—no, this is about experiencing the site the way users experience it. This audit is meant to track and record the structure of the site as it really works.

\n\n\n

Setting up the template

\n\n\n

First, we’re gonna need another spreadsheet. (Look, it is not my fault that spreadsheets are the perfect system for recording audit data. I don’t make the rules.)

\n\n\n

Because this involves building a spreadsheet from scratch, I keep a “template” at the top of my audit files—rows that I can copy and paste into each new audit (Fig 4.1). It’s a color-coded outline key that helps me track my page hierarchy and my place in the auditing process. When auditing thousands of pages, it’s easy to get dizzyingly lost, particularly when coming back into the sheet after a break; the key helps me stay oriented, no matter how deep the rabbit hole.

\n\n\n
\"\"
Fig 4.1: I use a color-coded outline key to record page hierarchy as I move through the audit. Wait, how many circles did Dante write about?
\n\n\n

Color-coding

\n\n\n

Color is the easiest, quickest way to convey page depth at a\nglance. The repetition of black text, white cells, and gray lines can have a\nnumbing effect—too many rows of sameness, and your eyes glaze over. My coloring\nmay result in a spreadsheet that looks like a twee box of macarons, but at\nleast I know, instantly, where I am.

\n\n\n

The exact colors don’t really matter, but I find that the\nfamiliar mental model of a rainbow helps with recognition—the cooler the row color,\nthe deeper into the site I know I must be.

\n\n\n

The nested rainbow of pages is great when you’re auditing neatly nested pages—but most websites color outside the lines (pun extremely intended) with their structure. I leave my orderly rainbow behind to capture duplicate pages, circular links, external navigation, and other inconsistencies like:

\n\n\n
  • On-page navigation. A bright text color denotes pages that are accessible via links within page content—not through the navigation. These pages are critical to site structure but are easily overlooked. Not every page needs to be displayed in the navigation menus, of course—news articles are a perfect example—but sometimes this indicates publishing errors.
  • External links. These are navigation links that go to pages outside the domain. They might be social media pages, or even sites held by the same company—but if the domain isn’t the one I’m auditing, I don’t need to follow it. I do need to note its existence in my spreadsheet, so I color the text as the red flag that it is. (As a general rule, I steer clients away from placing external links in navigation, in order to maintain a consistent experience. If there’s a need to send users offsite, I’ll suggest using a contextual, on-page link.)
  • Files. This mostly refers to PDFs, but can include Word files, slide decks, or anything else that requires downloading. As with external links, I want to capture anything that might disrupt the in-site browsing experience. (My audits usually filter out PDFs, but for organizations that overuse them, I’ll audit them separately to show how much “website” content is locked inside.)
  • Unknown hierarchy. Every once in a while, there’s a page that doesn’t seem to belong anywhere—maybe it’s missing from the menu, while its URL suggests it belongs in one section and its navigation scheme suggests another. These pages need to be discussed with their owners to determine whether the content needs to be considered in the new site.
  • Crosslinks. These are navigation links for pages that canonically live in a different section of the site—in other words, they’re duplicates. This often happens in footer navigation, which may repeat the main navigation or surface links to deeper-but-important pages (like a Contact page or a privacy policy). I don’t want to record the same information about the page twice, but I do need to know where the crosslink is, so I can track different paths to the content. I color these cells gray so they don’t draw my attention.
\n\n\n

Note that coloring every row (and indenting, as you’ll see in a moment) can be a tedious process—unless you rely on Excel’s formatting brush. That tool applies all the right styles in just two quick clicks.

\n\n\n

Outlines and page IDs

\n\n\n

Color-coding is half of my template; the other half is the outline, which is how I keep track of the structure itself. (No big deal, just the entire point of the spreadsheet.)

\n\n\n

Every page in the site gets assigned an ID. You are assigning this number; it doesn’t correspond to anything but your own perception of the navigation. This number does three things for you:

\n\n\n
  1. It associates pages with their place in the site hierarchy. Decimals indicate levels, so the page ID can be decoded as the page’s place in the system.
  2. It gives each page a unique identifier, so you can easily refer to a particular page—saying “2.4.1” is much clearer than “you know that one page in the fourth product category?”
  3. You can keep using the ID in other contexts, like your sitemap. Then, later, when your team decides to wireframe pages 1.1.1 and 7.0, you’ll all be working from the same understanding.
\n\n\n

Let me be completely honest: things might get goofy sometimes\nwith the decimal outline. There will come a day when you’ll find yourself\ncasually typing out “1.2.1.2.1.1.1,” and at that moment, a fellow auditor somewhere\nin the universe will ring a tiny gong for you.

\n\n\n

In addition to the IDs, I indent each level, which reinforces\nboth the numbers and the colors. Each level down—each digit in the ID, each\nchange in color—gets one indentation.

\n\n\n

I identify top-level pages with a single number: 1.0, 2.0, 3.0, etc. The next page level in the first section would be 1.1, 1.2, 1.3, and so on. I mark the homepage as 0.0, which is mildly controversial—the homepage is technically a level above—but, look: I’ve got a lot of numbers to write, and I don’t need those numbers to tell me they’re under the homepage, so this is my system. Feel free to use the numbering system that work best for you.

\n\n\n

Criteria and columns

\n\n\n

So we’ve got some secret codes for tracking hierarchy and depth, but what about other structural criteria? What are our spreadsheet columns (Fig 4.2)? In addition to a column for Page ID, here’s what I cover:

\n\n\n
  • URL.\nI don’t consistently fill out this column, because I already collected this\ndata back in my automated audit. I include it every twenty entries or so (and\non crosslinks or pages with unknown hierarchy) as another way of tracking\nprogress, and as a direct link into the site itself.
  • Menu\nlabel/link. I include this column only if I notice a lot of\nmismatches between links, labels, and page names. Perfect agreement isn’t\nrequired; but frequent, significant differences between the language that leads\nto a page and the language on the page itself may indicate\ninconsistencies in editorial approach or backend structures.
  • Name/headline.\nThink of this as “what does the page owner call it?” It may be\nthe H1, or an H2; it may match the link that brought you here, or the page\ntitle in the browser, or it may not.
  • Page\ntitle. This is for the name of the page in the metadata. Again,\nI don’t use this in every audit—particularly if the site uses the same long,\nbranded metadata title for every single page—but frequent mismatches can be\nuseful to track.
  • Section.\nWhile the template can indicate your level, it can’t tell you which area of the\nsite you’re in—unless you write it down. (This may differ from the section data\nyou applied to your automated audit, taken from the URL structure; here, you’re\nnoting the section where the page appears.)
  • Notes.\nFinally, I keep a column to note specific challenges, and to track patterns I’m\nseeing across multiple pages—things like “Different template, missing subnav”\nor “Only visible from previous page.” My only caution here is that if you’re planning\nto share this audit with another person, make sure your notes are—ahem—professional.\nUnless you enjoy anxiously combing through hundreds of entries to revise comments\nlike “Wow haha nope” (not that I would know anything about that).
\n\n\n
\"\"
Fig 4.2: A semi-complete structural audit. This view shows a lot of second- and third-level pages, as well as pages accessed through on-page navigation.
\n\n\n

Depending on your project needs, there may be other columns, too.\nIf, in addition to using this spreadsheet for your new sitemap, you want to use\nit in migration planning or template mapping, you may want columns for new\nURLs, or template types. 

\n\n\n

You can get your own copy of my template as a downloadable Excel file. Feel free to tweak it to suit your style and needs; I know I always do. As long as your spreadsheet helps you understand the hierarchy and structure of your website, you’re good to go.

\n\n\n

Gathering data

\n\n\n

Setting up the template is one thing—actually filling it out is, admittedly, another. So how do we go from a shiny, new, naive spreadsheet to a complete, jaded, seen-some-stuff spreadsheet? I always liked Erin Kissane’s description of the process, from The Elements of Content Strategy:

\n\n\n

Big inventories involve a lot of black coffee, a few late nights, and a playlist of questionable but cheering music prominently featuring the soundtrack of object-collecting video game Katamari Damacy. It takes quite a while to exhaustively inventory a large site, but it’s the only way to really understand what you have to work with.

\n\n\n

We’re not talking about the same kind of exhaustive inventory she\nwas describing (though I am recommending Katamari music). But even our\nless intensive approach is going to require your butt in a seat, your eyes on a\nscreen, and a certain amount of patience and focus. You’re about to walk, with\nyour fingers, through most of a website.

\n\n\n

Start on the homepage. (We know that not all users start there,\nbut we’ve got to have some kind of order to this process or we’ll never get\nthrough it.) Explore the main navigation before moving on to secondary\nnavigation structures. Move left to right, top to bottom (assuming that is your\nlanguage direction) over each page, looking for the links. You want to record\nevery page you can reasonably access on the site, noting navigational and\nstructural considerations as you go.

\n\n\n

My advice as you work:

\n\n\n
  • Use two monitors. I struggle immensely without two screens in this process, which involves constantly switching between spreadsheet and browser in rapid, tennis-match-like succession. If you don’t have access to multiple monitors, find whatever way is easiest for you to quickly flip between applications.
  • Record what you see. I generally note all visible menu links at the same level, then exhaust one section at a time. Sometimes this means I have to adjust what I initially observed, or backtrack to pages I missed earlier. You might prefer to record all data across a level before going deeper, and that would work, too. Just be consistent to minimize missed links.
  • Be alert to inconsistencies. On-page links, external links, and crosslinks can tell you a lot about the structure of the site, but they’re easy to overlook. Missed on-page links mean missed content; missed crosslinks mean duplicate work. (Note: the further you get into the site, the more you’ll start seeing crosslinks, given all the pages you’ve already recorded.)
  • Stick to what’s structurally relevant. A single file that’s not part of a larger pattern of file use is not going to change your understanding of the structure. Neither is recording every single blog post, quarterly newsletter, or news story in the archive. For content that’s dynamic, repeatable, and plentiful, I use an x in the page ID to denote more of the same. For example, a news archive with a page ID of 2.8 might show just one entry beneath it as 2.8.x; I don’t need to record every page up to 2.8.791 to understand that there are 791 articles on the site (assuming I noted that fact in an earlier content review).
  • Save. Save frequently. I cannot even begin to speak of the unfathomable heartbreak that is Microsoft Excel burning an unsaved audit to the ground.  
\n\n\n

Knowing which links to follow, which to record, and how best to\nuntangle structural confusion—that improves with time and experience. Performing\nstructural audits will not only teach you about your current site, but will\nhelp you develop fluency in systems thinking—a boon when it comes time to\ndocument the new site.

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/Ls-z54zDDFU/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/everyday-information-architecture-excerpt/","type":"text/html"}],"crawled":1569829037486,"published":1569829037486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d811850ee:1c46:90d684ff","keywords":["Community, Industry"],"originId":"https://alistapart.com/article/nothing-fails-like-success/","fingerprint":"9a31d3f6","title":"Nothing Fails Like Success","author":"by Jeffrey Zeldman","summary":{"content":"\n

A family buys a house they can’t afford. They can’t make their monthly mortgage payments, so they borrow money from the Mob. Now they’re in debt to the bank and the Mob, live in fear of losing their home, and must do whatever their creditors tell them to do.

\n\n\n

Welcome to the internet, 2019.

\n\n\n

Buying something you can’t afford, and borrowing from organizations that don’t have your (or your customers’) best interest at heart, is the business plan of most internet startups. It’s why our digital services and social networks in 2019 are a garbage fire of lies, distortions, hate speech, tribalism, privacy violations, snake oil, dangerous idiocy, deflected responsibility, and whole new categories of unpunished ethical breaches and crimes.

\n\n\n

From optimistically conceived origins and message statements about making the world a better place, too many websites and startups have become the leading edge of bias and trauma, especially for marginalized and at-risk groups.

\n\n\n

Why (almost) everything sucks

\n\n\n

Twitter, for instance, needs a lot of views for advertising to pay at the massive scale its investors demand. A lot of views means you can’t be too picky about what people share. If it’s misogynists or racists inspiring others who share their heinous beliefs to bring back the 1930s, hey, it’s measurable. If a powerful elected official’s out-of-control tweeting reduces churn and increases views, not only can you pay your investors, you can even take home a bonus. Maybe it can pay for that next meditation retreat.

\n\n\n

You can cloak this basic economic trade-off in fifty layers of bullshit—say you believe in freedom of speech, or that the antidote to bad speech is more speech—but the fact is, hate speech is profitable. It’s killing our society and our planet, but it’s profitable. And the remaining makers of Twitter—the ones whose consciences didn’t send them packing years ago—no longer have a choice. The guy from the Mob is on his way over, and the vig is due.

\n\n\n

Not to single out Twitter, but this is clearly the root cause of its seeming indifference to the destruction hate speech is doing to society…and will ultimately do to the platform. (But by then Jack will be able to afford to meditate full-time.)

\n\n\n

Other companies do other evil things to pay their vig. When you owe the Mob, you have no choice. Like sell our data. Or lie about medical research.

\n\n\n

There are internet companies (like Basecamp, or like Automattic, makers of WordPress.com, where I work) that charge money for their products and services, and use that money to grow their business. I wish more internet companies could follow that model, but it’s hard to retrofit a legitimate business model to a product that started its life as free.

\n\n\n

And there are even some high-end news publications, such as The New York Times, The Washington Post, and The Guardian, that survive on a combination of advertising and flexible paywalls. But these options are not available to most digital publications and businesses.

\n\n\n

Return with me to those Halcyon days…

\n\n\n

Websites and internet startups used to be you and your friends making cool stuff for your other friends, and maybe building new friendships and even small communities in the process. (Even in 2019, that’s still how some websites and startups begin—as labors of love, fashioned by idealists in their spare time.)

\n\n\n

Because they are labors of love; because we’ve spent 25 years training people to believe that websites, and news, and apps, and services should be free; because, when we begin a project, we can scarcely believe anyone will ever notice or care about it—for these reasons and more, the things we make digitally, especially on the web, are offered free of charge. We labor on, excited by positive feedback, and delighted to discover that, if we keep at it, our little community will grow.

\n\n\n

Most such labors of love disappear after a year or two, as the creators drift out of touch with each other, get “real” jobs, fall in love, start families, or simply lose interest due to lack of attention from the public or the frustrations of spending weekends and holidays grinding away at an underappreciated site or app while their non-internet friends spend those same hours either having fun or earning money.

\n\n\n

Along came money

\n\n\n

But some of these startup projects catch on. And when they do, a certain class of investor smells ROI. And the naive cofounders, who never expected their product or service to really get anywhere, can suddenly envision themselves rich and Zuckerberg-famous. Or maybe they like the idea of quitting their day job, believing in themselves, and really going for it. After all, that is an empowering and righteous vision.

\n\n\n

Maybe they believe that by taking the initial investment, they can do more good—that their product, if developed further, can actually help people. This is often the motivation behind agreeing to an initial investment deal, especially in categories like healthcare.

\n\n\n

Or maybe the founders are problem solvers. Existing products or services in a given category have a big weakness. The problem solvers are sure that their idea is better. With enough capital, and a slightly bigger team, they can show the world how to do it right. Most inventions that have moved humankind forward followed exactly this path. It should lead to a better world (and it sometimes does). It shouldn’t produce privacy breaches and fake medicine and election-influencing bots and all the other plagues of our emerging digital civilization. So why does it?

\n\n\n

Content wants to be paid

\n\n\n

Primarily it is because these businesses have no business model. They were made and given away free. Now investors come along who can pay the founders, buy them an office, give them the money to staff up, and even help with PR and advertising to help them grow faster.

\n\n\n

Now there are salaries and insurance and taxes and office space and travel and lecture tours and sales booths at SXSW, but there is still no charge for the product.

\n\n\n

And the investor seeks a big return.

\n\n\n

And when the initial investment is no longer enough to get the free-product company to scale to the big leagues, that’s when the really big investors come in with the really big bucks. And the company is suddenly famous overnight, and “everybody” is using the product, and it’s still free, and the investors are still expecting a giant payday.

\n\n\n

Like I said—a house you can’t afford, so you go into debt to the bank and the Mob.

\n\n\n

The money trap

\n\n\n

Here it would be easy to blame capitalism, or at least untrammeled, under-regulated capitalism, which has often been a source of human suffering—not that capitalism, properly regulated, can’t also be a force for innovation which ameliorates suffering. That’s the dilemma for our society, and where you come down on free markets versus governmental regulation of businesses should be an intellectual decision, but these days it is a label, and we hate our neighbors for coming down a few degrees to the left or right of us. But I digress and oversimplify, and this isn’t a complaint about late stage capitalism per se, although it may smell like one.

\n\n\n

No, the reason small companies created by idealists too frequently turn into consumer-defrauding forces for evil has to do with the amount of profit each new phase of investor expects to receive, and how quickly they expect to receive it, and the fact that the products and services are still free. And you know what they say about free products.

\n\n\n

Nothing fails like success

\n\n\n

A friend who’s a serial entrepreneur has started maybe a dozen internet businesses over the span of his career. They’ve all met a need in the marketplace. As a consequence, they’ve all found customers, and they’ve all made a profit. Yet his investors are rarely happy.

\n\n\n

“Most of my startups have the decency to fail in the first year,” one investor told him. My friend’s business was taking in several million dollars a year and was slowly growing in staff and customers. It was profitable. Just not obscenely so.

\n\n\n

And internet investors don’t want a modest return on their investment. They want an obscene profit right away, or a brutal loss, which they can write off their taxes. Making them a hundred million for the ten million they lent you is good. Losing their ten million is also good—they pay a lower tax bill that way, or they use the loss to fold a company, or they make a profit on the furniture while writing off the business as a loss…whatever rich people can legally do under our tax system, which is quite a lot.

\n\n\n

What these folks don’t want is to lend you ten million dollars and get twelve million back.

\n\n\n

You and I might go, “Wow! I just made two million dollars just for being privileged enough to have money to lend somebody else.” And that’s why you and I will never have ten million dollars to lend anybody. Because we would be grateful for it. And we would see a free two million dollars as a life-changing gift from God. But investors don’t think this way.

\n\n\n

We didn’t start the fire, but we roasted our weenies in it

\n\n\n

As much as we pretend to be a religious nation, our society worships these investors and their profits, worships companies that turn these profits, worships above all the myth of overnight success, which we use to motivate the hundreds of thousands of workers who will work nights and weekends for the owners in hopes of cashing in when the stock goes big.

\n\n\n

Most times, even if the stock does go big, the owner has found a way to devalue it by the time it does. Owners have brilliant advisers they pay to figure out how to do those things. You and I don’t.

\n\n\n

A Christmas memory

\n\n\n

I remember visiting San Francisco years ago and scoring an invitation to Twitter’s Christmas party through a friend who worked there at the time. Twitter was, at the time, an app that worked via SMS and also via a website. Period.

\n\n\n

Some third-party companies, starting with my friends at Iconfactory, had built iPhone apps for people who wanted to navigate Twitter via their newfangled iPhones instead of the web. Twitter itself hadn’t publicly addressed mobile and might not even have been thinking about it.

\n\n\n

Although Twitter was transitioning from a fun cult thing—used by bloggers who attended SXSW Interactive in 2007—to an emerging cultural phenomenon, it was still quite basic in its interface and limited in its abilities. Which was not a bad thing. There is art in constraint, value in doing one thing well. As an outsider, if I’d thought about it, I would have guessed that Twitter’s entire team consisted of no more than 10 or 12 wild-eyed, sleep-deprived true believers.

\n\n\n

Imagine my surprise, then, when I showed up at the Christmas party and discovered I’d be sharing dinner with hundreds of designers, developers, salespeople, and executives instead of the handful I’d naively anticipated meeting. (By now, of course, Twitter employs many thousands. It’s still not clear to an outsider why so many workers are needed.)

\n\n\n

But one thing is clear: somebody has to pay for it all.

\n\n\n

Freemium isn’t free

\n\n\n

Employees, let alone thousands of them, on inflated Silicon Valley engineer salaries, aren’t free. Health insurance and parking and meals and HR and travel and expense accounts and meetups and software and hardware and office space and amenities aren’t free. Paying for all that while striving to repay investors tenfold means making a buck any way you can.

\n\n\n

Since the product was born free and a paywall isn’t feasible, Twitter must rely on that old standby: advertising. Advertising may not generate enough revenue to keep your hometown newspaper (or most podcasts and content sites) in business, but at Twitter’s scale, it pays.

\n\n\n

It pays because Twitter has so many active users. And what keeps those users coming back? Too often, it’s the dopamine of relentless tribalism—folks whose political beliefs match and reinforce mine in a constant unwinnable war of words with folks whose beliefs differ.

\n\n\n

Of course, half the antagonists in a given brawl may be bots, paid for in secret by an organization that wants to make it appear that most citizens are against Net Neutrality, or that most Americans oppose even the most basic gun laws, or that our elected officials work for lizard people. The whole system is broken and dangerous, but it’s also addictive, and we can’t look away. From our naive belief that content wants to be free, and our inability to create businesses that pay for themselves, we are turning our era’s greatest inventions into engines of doom and despair.

\n\n\n

Your turn

\n\n\n

So here we are. Now what do we do about it?

\n\n\n

It’s too late for current internet businesses (victims of their own success) that are mortgaged to the hilt in investor gelt. But could the next generation of internet startups learn from older, stable companies like Basecamp, and design products that pay for themselves via customer income—products that profit slowly and sustainably, allowing them to scale up in a similarly slow, sustainable fashion?

\n\n\n

The self-payment model may not work for apps and sites that are designed as modest amusements or communities, but maybe those kinds of startups don’t need to make a buck—maybe they can simply be labors of love, like the websites we loved in the 1990s and early 2000s.

\n\n\n

Along those same lines, can the IndieWeb, and products of IndieWeb thinking like Micro.blog, save us? Might they at least provide an alternative to the toxic aspects of our current social web, and restore the ownership of our data and content? And before you answer, RTFM.

\n\n\n

On an individual and small collective basis, the IndieWeb already works. But does an IndieWeb approach scale to the general public? If it doesn’t scale yet, can we, who envision and design and build, create a new generation of tools that will help give birth to a flourishing, independent web? One that is as accessible to ordinary internet users as Twitter and Facebook and Instagram? Tantek Çelik thinks so, and he’s been right about the web for nearly 30 years. (For more about what Tantek thinks, listen to our conversation in Episode № 186 of The Big Web Show.)
Are these approaches mere whistling against a hurricane? Are most web and internet users content with how things are? What do you think? Share your thoughts on your personal website (dust yours off!) or (irony ahoy!) on your indie or mainstream social networks of choice using hashtag #LetsFixThis. I can’t wait to see what you have to say.

\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/11S-0ER8qtM/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/nothing-fails-like-success/","type":"text/html"}],"crawled":1569828917486,"published":1569828917486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d81167c2e:1c45:90d684ff","keywords":["Accessibility, User Experience"],"originId":"https://alistapart.com/article/accessibility-for-vestibular/","fingerprint":"4e4d4d7e","title":"Accessibility for Vestibular Disorders: How My Temporary Disability Changed My Perspective","author":"by Facundo Corradini","summary":{"content":"\n

Accessibility can be tricky. There are plenty of conditions to take into consideration, and many technical limitations and weird exceptions that make it quite hard to master for most designers and developers.

\n\n\n

I never considered myself an accessibility expert, but I took great pride in making my projects Web Content Accessibility Guidelines (WCAG) compliant…ish. They would pass most automated tests, show perfectly in the accessibility tree, and work quite well with keyboard navigation. I would even try (and fail) to use a screen reader every now and then.

\n\n\n

But life would give me a lesson I would probably never learn otherwise: last October, my abled life took a drastic change—I started to feel extremely dizzy, with a constant sensation of falling or spinning to the right. I was suffering from a bad case of vertigo caused by labyrinthitis that made it impossible to get anything done.

\n\n\n

Vertigo can have a wide range of causes, the most common being a viral infection or tiny calcium crystal free floating in the inner ear, which is pretty much our body’s accelerometer. Any disruption in there sends the brain confusing signals about the body’s position, which causes really heavy nausea, dizziness, and headaches. If you’ve ever felt seasick, it’s quite a similar vibe. If not, think about that feeling when you just get off a rollercoaster…it’s like that, only all day long.

\n\n\n

For most people, vertigo is something they’ll suffer just once in a lifetime, and it normally goes away in a week or two. Incidence is really high, with some estimates claiming that up to 40% of the population suffers vertigo at least once in their lifetime. Some people live all their lives with it (or with similar symptoms caused by a range of diseases and syndromes grouped under the umbrella term of vestibular disorders), with 4% of US adults reporting chronic problems with balance, and an additional 1.1% reporting chronic dizziness, according to the American Speech-Language-Hearing Association.

\n\n\n

In my case, it was a little over a month. Here’s what I learned while going through it.

\n\n\n

Slants can trigger vestibular symptoms

\n\n\n

It all started as I was out for my daily jog. I felt slightly dizzy, then suddenly my vision got totally distorted. Everything appeared further away, like looking at a fun house’s distortion mirror. I stumbled back home and rested; at that moment I believed I might have over-exercised, and that hydration, food, and rest were all I needed. Time would prove me wrong.

\n\n\n

What I later learned was that experiencing vertigo is a constant war between one of your inner ears telling the brain “everything is fine, we’re level and still” and the other ear shouting “oh my God, we’re falling, we’re falling!!!” Visual stimuli can act as an intermediary, supporting one ear’s message or the other’s. Vertigo can also work in the opposite way, with the dizziness interfering with your vision.

\n\n\n

I quickly found that when symptoms peaked, staring at a distant object would ease the falling sensation somewhat.

\n\n\n

In the same fashion, some visual stimuli would worsen it.

\n\n\n

Vertical slants were a big offender in that sense. For instance, looking at a subtle vertical slant (the kind that you’d have to look at twice to make sure it’s not perfectly vertical) on a webpage would instantly trigger symptoms for me. Whether it was a page-long slant used to create some interest beside text or a tiny decoration to mark active tabs, looking at anything with slight slants would instantly send me into the rollercoaster.

\n\n\n

Horizontal slants (whatever the degree) and harder vertical slants wouldn’t cause these issues.

\n\n\n

My best guess is that slight vertical slants can look like forced perspective and therefore reinforce the falling-from-height sensation, so I would recommend avoiding vertical slants if you can, or make them super obvious. A slight slant looks like perspective, a harder one looks like a triangle.

\n\n\n

Target size matters (even on mouse-assisted devices)

\n\n\n

After a magnetic resonance imaging (MRI) scan, some tests to discard neurological conditions, and other treatments that proved ineffective, I was prescribed Cinnarizine.

\n\n\n

Cinnarizine is a calcium channel blocker—to put it simply, it prevents the malfunctioning inner ear “accelerometer” from sending incorrect info to the brain. 
And it worked wonders. After ten days of being barely able to get out of bed, I was finally getting something closer to my normal life. I would still feel dizzy all the time, with some peaks throughout the day, but for the most part, it was much easier.

\n\n\n

At this point, I was finally able to use the computer (but still unable to produce any code at all). To make the best of it, I set on a mission to self-experiment on accessibility for vestibular disorders. In testing, I found that one of the first things that struck me was that I would always miss targets (links and buttons).

\n\n\n

I’m from the generation that grew up with desktop computers, so using a mouse is second nature. The pointer is pretty much an extension of my mind, as it is for many who use it regularly. But while Cinnarizine helped with the dizziness, it has a common side effect of negatively impacting coordination and fine motor skills (it is recommended not to drive or operate machinery while under treatment). It was not a surprise when I realized it would be much harder to get the pointer to do what I intended.

\n\n\n

The common behavior would be: moving the pointer past the link I intended to click, clicking before reaching it at all, or having to try multiple times to click on smaller targets.

\n\n\n

Success Criterion 2.5.5 Target Size (Level AAA) of the World Wide Web Consortium (W3C)’s WCAG recommends bigger target sizes so users can activate them easily. The obvious reason for this is that it’s harder to pinpoint targets on smaller screens with coarser inputs (i.e., touchscreens of mobile devices). A fairly common practice for developers is to set bigger target sizes for smaller viewport widths (assuming that control challenges are only touch-related), while neglecting the issue on big screens expected to be used with mouse input. I know I’m guilty of that myself.

\n\n\n

Instead of targeting this behavior for just smaller screen sizes, there are plenty of reasons to create larger target sizes on all devices: it will benefit users with limited vision (when text is scaled up accordingly and colors are of sufficient contrast), users with mobility impairments such as hand tremors, and of course, users with difficulty with fine motor skills.

\n\n\n

Font size and spacing

\n\n\n

Even while “enjoying” the ease of symptoms provided by the treatment, reading anything still proved to be a challenge for the following three weeks.

\n\n\n

I was completely unable to use mobile devices while suffering vertigo due to the smaller font sizes and spacing, so I was forced to use my desktop computer for everything.

\n\n\n

I can say I was experiencing something similar to users with mild forms of dyslexia or attention disorders: whenever I got to a website that didn’t follow good font styling, I would find myself reading the same line over and over again.

\n\n\n

This proves once again that accessibility is intersectional: when we improve things for a particular purpose it usually benefits users with other challenges as well. I used to believe recommendations on font styles were mostly intended for the nearsighted and those who have dyslexia. Turns out they are also critical for those with vertigo, and even for those with some cognitive differences. At the end of the day, everybody benefits from better readability.

\n\n\n

Some actions you can take to improve readability are:

\n\n\n
  • Keep line height to at least 1.5 times the font size (i.e., line-height: 1.5).
  • Set the spacing between paragraphs to at least 2.0 times the font size. We can do this by adjusting the margins using relative units such as em.
  • Letter spacing should be at least 0.12 times the font size. We can adjust this by using the letter-spacing CSS property, perhaps setting it in a relative unit.
  • Make sure to have good contrast between text and its background.
  • Keep font-weight at a reasonable level for the given font-family. Some fonts have thin strokes that make them harder to read. When using thinner fonts, try to improve contrast and font size accordingly, even more than what WCAG would suggest.
  • Choose fonts that are easy to read. There has been a large and still inconclusive debate on which font styles are better for users, but one thing I can say for sure is that popular fonts (as in fonts that the user might be already familiar with) are generally the least challenging for users with reading issues.
\n\n\n

WCAG recommendations on text are fairly clear and fortunately are the most commonly implemented of recommendations, but even they can still fall short sometimes. So, better to follow specific guides on accessible text and your best judgement. Passing automated tests does not guarantee actual accessibility.

\n\n\n

Another issue on which my experience with vertigo proved to be similar to that of people with dyslexia and attention disorders was how hard it was for me to keep my attention in just one place. In that sense…

\n\n\n

Animations are bad (and parallax is pure evil)

\n\n\n

Val Head has already covered visually-triggered vestibular disorders in an outstanding article, so I would recommend giving it a good read if you haven’t already.

\n\n\n

To summarize, animations can trigger nausea, dizziness, and headaches in some users, so we should use them purposely and responsibly.

\n\n\n

While most animations did not trigger my symptoms, parallax scrolling did. I’d never been a fan of parallax to begin with, as I found it confusing. And when you’re experiencing vertigo, the issues introduced by parallax scrolling compound.

\n\n\n

Really, there are no words to describe just how bad a simple parallax effect, scrolljacking, or even background-attachment: fixed would make me feel. I would rather jump on one of those 20-G centrifuges astronauts use than look at a website with parallax scrolling.

\n\n\n

Every time I encountered it, I would put the bucket beside me to good use and be forced to lie in bed for hours as I felt the room spinning around me, and no meds could get me out of it. It was THAT bad.

\n\n\n

Though normal animations did not trigger a reaction as severe, they still posed a big problem. The extreme, conscious, focused effort it took to read would make it such that anything moving on the screen would instantly break my focus, and force me to start the paragraph all over. And I mean anything.

\n\n\n

I would constantly find myself reading a website only to have the typical collapsing navigation bar on scroll distract me just enough that I’d totally lose count of where I was at. Autoplaying carousels were so annoying I would delete them using dev tools as soon as they showed up. Background videos would make me get out of the website desperately.

\n\n\n

Over time I started using mouse selection as a pointer; a visual indication of what I’d already read so I could get back to it whenever something distracted me. Then I tried custom stylesheets to disable transforms and animations whenever possible, but that also meant many websites having critical elements not appear at all, as they were implemented to start off-screen or otherwise invisible, and show up on scroll.

\n\n\n

Of course, deleting stuff via dev tools or using custom stylesheets is not something we can expect 99.99% of our users to even know about.

\n\n\n

So if anything, consider reducing animations to a minimum. Provide users with controls to turn off non-essential animations (WCAG 2.2.3 Animation from Interactions) and to pause, stop, or hide them (WCAG 2.2.2 Pause, Stop, Hide). Implement animations and transitions in such a way that if the user disables them, critical elements still display.

\n\n\n

And be extra careful with parallax: my recommendation is to, at the very least, try limiting its use to the header (“hero”) only, and be mindful of getting a smooth, realistic parallax experience. My vertigo self would have said, “just don’t freaking use parallax. Never. EVER.” But I guess that might be a hard idea to sell to stakeholders and designers.

\n\n\n

Also consider learning how to use the prefers-reduced-motion feature query. This is a newer addition to the specs (it’s part of the Media Queries Level 5 module , which is at an early Editor’s Draft stage) that allows authors to apply selective styling depending on whether the user has requested the system to minimize the use of animations. OS and browser support for it is still quite limited, but the day will come when we will set any moving thing inside a query for when the user has no-preference, blocking animations from those who choose reduce.

\n\n\n

After about a week of wrestling websites to provide a static experience, I remembered something that would prove to be my biggest ally while the vertigo lasted:

\n\n\n

Reader mode

\n\n\n

Some browsers include a “reader mode” that strips the content from any styling choices, isolates it from any distraction, and provides a perfect WCAG compliant layout for the text to maximize readability.

\n\n\n

It is extremely helpful to provide a clear and consistent reading experience throughout multiple websites, especially for users with any kind of reading impairment.

\n\n\n

I have to confess: before experiencing my vestibular disorder, I had never used Reader Mode (the formal name varies in browsers) or even checked if my projects were compatible with it. I didn’t even think it was such a useful feature, as a quick search for “reader mode” actually returned quite a few threads by users asking how to disable it or how to take the button for it out of Firefox’s address bar. (It seems some people are unwittingly activating it…perhaps the icon is not clear enough.)

\n\n\n

Displaying the button to access Reader Mode is toggled by browser heuristics, which are based on the use (or not) of semantic tags in a page’s HTML. Unfortunately this meant not all websites provided such a “luxury.”

\n\n\n

I really wish I wouldn’t have to say this in 2019…but please, please use semantic tags. Correct conversational semantics allow your website to be displayed in Reader Mode, and provide a better experience for users of screen readers. Again, accessibility is intersectional.

\n\n\n

Reader Mode proved to be extremely useful while my vertigo lasted. But there was something even better:

\n\n\n

Dark color schemes

\n\n\n

By the fourth week, I started feeling mostly fine. I opened Visual Studio Code to try to get back to work. In doing so, it served me well to find one more revelation: a light-text-on-dark-background scheme was SO much easier for me to read. (Though I still was not able to return to work at this time.)

\n\n\n

I was quite surprised, as I had always preferred light mode with dark-text-on-light-background for reading, and dark mode, with light-text-on-dark for coding. I didn’t know at the time that I was suffering from photophobia (which is a sensitivity to light), which was one of the reasons I found it hard to read on my desktop and to use my mobile device at all.

\n\n\n

As far as I know, photophobia is not a common symptom of vestibular disorders, but there are many conditions that will trigger it, so it’s worth looking into for our projects’ accessibility.

\n\n\n

CSS is also planning a media query to switch color schemes. Known as prefers-color-scheme, it allows applying styles based on the user’s stated preference for dark or light theming. It’s also part of the Media Queries Level 5 spec, and at the time of writing this article it’s only available in Safari Technology Preview, with Mozilla planning to ship it in the upcoming Firefox 67. Luckily there’s a PostCSS plugin that allows us to use it in most modern browsers by turning prefers-color-schemequeries into color-index queries, which have much better support.

\n\n\n

If PostCSS is not your cup of tea, or for whatever reason you cannot use that approach to automate switching color schemes to a user’s preference, try at least to provide a theming option in your app’s configuration. Theming has become extremely simple since the release of CSS Custom Properties, so implementing this sort of switch is relatively easy and will greatly benefit anyone experiencing photophobia.

\n\n\n

Moving on

\n\n\n

After a month and some days, the vertigo disappeared completely, and I was able to return to work without needing any meds or further treatment. It should stay that way, as for most people it’s a once-in-a-lifetime occurrence.

\n\n\n

I went back to my abled life, but the experience changed my mindset for good.

\n\n\n

As I said before, I always cared for making my projects compatible for people using keyboard navigation and screen readers. But I learned the hard way that there are plenty of “invisible conditions” that are just as important to take into consideration: vestibular disorders, cognitive differences, dyslexia, and color blindness, just to name a few. I was totally neglecting those most of the time, barely addressing the issues in order to pass automated tests, which means I was unintentionally annoying some users by making websites inaccessible to them.

\n\n\n

After my experience with vertigo, I’ve turned to an accessibility-first approach to design and development. Now I ask myself, “am I leaving anyone behind with this decision?,” before dropping a single line of code. Accessibility should never be an afterthought.

\n\n\n

Making sure my projects work from the start for those with difficulties also improves the experience for everyone else. Think about how improving text styles for users with dyslexia, vertigo, or visual problems improves readability for all users, or how being able to control animations or choose a color scheme can be critical for users with attention disorders and photophobia, respectively, while also a nice feature for everybody.

\n\n\n

It also turned my workflow into a much smoother development experience, as addressing accessibility issues from the beginning can mean a slower start, but it’s also much easier and faster than trying to fix broken accessibility afterwards.

\n\n\n

I hope that by sharing my personal experience with vertigo, I’ve illustrated how we can all design and develop a better web for everybody. Remember, we’re all just temporarily abled.

\n\n\n


\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/P6NK__5U2eQ/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/accessibility-for-vestibular/","type":"text/html"}],"crawled":1569828797486,"published":1569828797486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"Yn8gu9QqU/Nu8FbPliaB+X/354R2g5xNVMORFC3HITI=_16d8114a76e:1c44:90d684ff","keywords":["Application Development, JavaScript"],"originId":"https://alistapart.com/article/responsible-javascript-part-1/","fingerprint":"6e3f673d","title":"Responsible JavaScript: Part I","author":"by Jeremy Wagner","summary":{"content":"\n

By the numbers, JavaScript is a performance liability. If the trend persists, the median page will be shipping at least 400 KB of it before too long, and that’s merely what’s transferred. Like other text-based resources, JavaScript is almost always served compressed—but that might be the only thing we’re getting consistently right in its delivery.

\n\n\n

Unfortunately, while reducing resource transfer time is a big part of that whole performance thing, compression has no effect on how long browsers take to process a script once it arrives in its entirety. If a server sends 400 KB of compressed JavaScript, the actual amount browsers have to process after decompression is north of a megabyte. How well devices cope with these heavy workloads depends, well, on the deviceMuch has been written about how adept various devices are at processing lots of JavaScript, but the truth is, the amount of time it takes to process even a trivial amount of it varies greatly between devices.

\n\n\n

Take, for example, this throwaway project of mine, which serves around 23 KB of uncompressed JavaScript. On a mid-2017 MacBook Pro, Chrome chews through this comparably tiny payload in about 25 ms. On a Nokia 2 Android phone, however, that figure balloons to around 190 ms. That’s not an insignificant amount of time, but in either case, the page gets interactive reasonably fast.

\n\n\n

Now for the big question: how do you think that little Nokia 2 does on an average page? It chokes. Even on a fast connection, browsing the web on it is an exercise in patience as JavaScript-laden web pages brick it for considerable stretches of time.

\n\n\n
\"A
Figure 1. A performance timeline overview of a Nokia 2 Android phone browsing on a page where excessive JavaScript monopolizes the main thread.
\n\n\n

While devices and the networks they navigate the web on are largely improving, we’re eating those gains as trends suggest. We need to use JavaScript responsibly. That begins with understanding what we’re building as well as how we’re building it.

\n\n\n

The mindset of “sites” versus “apps”

\n\n\n

Nomenclature can be strange in that we sometimes loosely identify things with terms that are inaccurate, yet their meanings are implicitly understood by everyone. Sometimes we overload the term “bee” to also mean “wasp”, even though the differences between bees and wasps are substantial. Those differences can motivate you to deal with each one differently. For instance, we’ll want to destroy a wasp nest, but because bees are highly beneficial and vulnerable insects, we may opt to relocate them.

\n\n\n

We can be just as fast and loose in interchanging the terms “website” and “web app”. The differences between them are less clear than those between yellowjackets and honeybees, but conflating them can bring about painful outcomes. The pain comes in the affordances we allow ourselves when something is merely a “website” versus a fully-featured “web app.” If you’re making an informational website for a business, you’re less likely to lean on a powerful framework to manage changes in the DOM or implement client-side routing—at least, I hope. Using tools so ill-suited for the task would not only be a detriment to the people who use that site but arguably less productive.

\n\n\n

When we build a web app, though, look out. We’re installing packages which usher in hundreds—if not thousands—of dependencies, some of which we’re not sure are even safe. We’re also writing complicated configurations for module bundlers. In this frenzied, yet ubiquitous, sort of dev environment, it takes knowledge and vigilance to ensure what gets built is fast and accessible. If you doubt this, run npm ls --prod in your project’s root directory and see if you recognize everything in that list. Even if you do, that doesn’t account for third party scripts—of which I’m sure your site has at least a few.

\n\n\n

What we tend to forget is that the environment websites and web apps occupy is one and the same. Both are subject to the same environmental pressures that the large gradient of networks and devices impose. Those constraints don’t suddenly vanish when we decide to call what we build “apps”, nor do our users’ phones gain magical new powers when we do so.

\n\n\n

It’s our responsibility to evaluate who uses what we make, and accept that the conditions under which they access the internet can be different than what we’ve assumed. We need to know the purpose we’re trying to serve, and only then can we build something that admirably serves that purpose—even if it isn’t exciting to build.

\n\n\n

That means reassessing our reliance on JavaScript and how the use of it—particularly to the exclusion of HTML and CSS—can tempt us to adopt unsustainable patterns which harm performance and accessibility.

\n\n\n

Don’t let frameworks force you into unsustainable patterns

\n\n\n

I’ve been witness to some strange discoveries in codebases when working with teams that depend on frameworks to help them be highly productive. One characteristic common among many of them is that poor accessibility and performance patterns often result. Take the React component below, for example:

\n\n\n
import React, { Component } from "react";\nimport { validateEmail } from "helpers/validation";\n\nclass SignupForm extends Component {\n  constructor (props) {\n    super(props);\n\n    this.handleSubmit = this.handleSubmit.bind(this);\n    this.updateEmail = this.updateEmail.bind(this);\n    this.state.email = "";\n  }\n\n  updateEmail (event) {\n    this.setState({\n      email: event.target.value\n    });\n  }\n\n  handleSubmit () {\n    // If the email checks out, submit\n    if (validateEmail(this.state.email)) {\n      // ...\n    }\n  }\n\n  render () {\n    return (\n      <div>\n        <span class="email-label">Enter your email:</span>\n        <input type="text" id="email" onChange={this.updateEmail} />\n        <button onClick={this.handleSubmit}>Sign Up</button>\n      </div>\n    );\n  }\n}
\n\n\n

There are some notable accessibility issues here:

\n\n\n
  1. A form that doesn’t use a <form> element is not a form. Indeed, you could paper over this by specifying role="form" in the parent <div>, but if you’re building a form—and this sure looks like one—use a <form> element with the proper action and method attributes. The action attribute is crucial, as it ensures the form will still do something in the absence of JavaScript—provided the component is server-rendered, of course.
  2. <span> is not a substitute for a <label> element, which provides accessibility benefits <span>s don’t.
  3. If we intend to do something on the client side prior to submitting a form, then we should move the action bound to the <button> element's onClick handler to the <form> element’s onSubmit handler.
  4. Incidentally, why use JavaScript to validate an email address when HTML5 offers form validation controls in almost every browser back to IE 10? There’s an opportunity here to rely on the browser and use an appropriate input type, as well as the required attribute—but be aware that getting this to work right with screen readers takes a little know-how.
  5. While not an accessibility issue, this component doesn't rely on any state or lifecycle methods, which means it can be refactored into a stateless functional component, which uses considerably less JavaScript than a full-fledged React component.
\n\n\n

Knowing these things, we can refactor this component:

\n\n\n
import React from "react";\n\nconst SignupForm = props => {\n  const handleSubmit = event => {\n    // Needed in case we're sending data to the server XHR-style\n    // (but will still work if server-rendered with JS disabled).\n    event.preventDefault();\n\n    // Carry on...\n  };\n  \n  return (\n    <form method="POST" action="/signup" onSubmit={handleSubmit}>\n      <label for="email" class="email-label">Enter your email:</label>\n      <input type="email" id="email" required />\n      <button>Sign Up</button>\n    </form>\n  );\n};
\n\n\n

Not only is this component now more accessible, but it also uses less JavaScript. In a world that’s drowning in JavaScript, deleting lines of it should feel downright therapeutic. The browser gives us so much for free, and we should try to take advantage of that as often as possible.

\n\n\n

This is not to say that inaccessible patterns occur only when frameworks are used, but rather that a sole preference for JavaScript will eventually surface gaps in our understanding of HTML and CSS. These knowledge gaps will often result in mistakes we may not even be aware of. Frameworks can be useful tools that increase our productivity, but continuing education in core web technologies is essential to creating usable experiences, no matter what tools we choose to use.

\n\n\n

Rely on the web platform and you’ll go far, fast

\n\n\n

While we’re on the subject of frameworks, it must be said that the web platform is a formidable framework of its own. As the previous section showed, we’re better off when we can rely on established markup patterns and browser features. The alternative is to reinvent them, and invite all the pain such endeavors all but guarantee us, or worse: merely assume that the author of every JavaScript package we install has solved the problem comprehensively and thoughtfully.

\n\n\n

SINGLE PAGE APPLICATIONS

\n\n\n

One of the tradeoffs developers are quick to make is to adopt the single page application (SPA) model, even if it’s not a fit for the project. Yes, you do gain better perceived performance with the client-side routing of an SPA, but what do you lose? The browser’s own navigation functionality—albeit synchronous—provides a slew of benefits. For one, history is managed according to a complex specification. Users without JavaScript—be it by their own choice or not—won’t lose access altogether. For SPAs to remain available when JavaScript is not, server-side rendering suddenly becomes a thing you have to consider.

\n\n\n
\"Two
Figure 2. A comparison of an example app loading on a slow connection. The app on the left depends entirely upon JavaScript to render a page. The app on the right renders a response on the server, but then uses client-side hydration to attach components to the existing server-rendered markup.
\n\n\n

Accessibility is also harmed if a client-side router fails to let people know what content on the page has changed. This can leave those reliant on assistive technology to suss out what changes have occurred on the page, which can be an arduous task.

\n\n\n

Then there’s our old nemesis: overhead. Some client-side routers are very small, but when you start with Reacta compatible router, and possibly even a state management library, you’re accepting that there’s a certain amount of code you can never optimize away—approximately 135 KB in this case. Carefully consider what you’re building and whether a client side router is worth the tradeoffs you’ll inevitably make. Typically, you’re better off without one.

\n\n\n

If you’re concerned about the perceived navigation performance, you could lean on rel=prefetch to speculatively fetch documents on the same origin. This has a dramatic effect on improving perceived loading performance of pages, as the document is immediately available in the cache. Because prefetches are done at a low priority, they’re also less likely to contend with critical resources for bandwidth.

\n\n\n
\"Screenshot
Figure 3. The HTML for the writing/ URL is prefetched on the initial page. When the writing/ URL is requested by the user, the HTML for it is loaded instantaneously from the browser cache.
\n\n\n

The primary drawback with link prefetching is that you need to be aware that it can be potentially wasteful. Quicklink, a tiny link prefetching script from Google, mitigates this somewhat by checking if the current client is on a slow connection—or has data saver mode enabled—and avoids prefetching links on cross-origins by default.

\n\n\n

Service workers are also hugely beneficial to perceived performance for returning users, whether we use client side routing or not—provided you know the ropesWhen we precache routes with a service worker, we get many of the same benefits as link prefetching, but with a much greater degree of control over requests and responses. Whether you think of your site as an “app” or not, adding a service worker to it is perhaps one of the most responsible uses of JavaScript that exists today.

\n\n\n

JAVASCRIPT ISN’T THE SOLUTION TO YOUR LAYOUT WOES

\n\n\n

If we’re installing a package to solve a layout problem, proceed with caution and ask “what am I trying to accomplish?” CSS is designed to do this job, and requires no abstractions to use effectively. Most layout issues JavaScript packages attempt to solve, like box placement, alignment, and sizingmanaging text overflow, and even entire layout systems, are solvable with CSS today. Modern layout engines like Flexbox and Grid are supported well enough that we shouldn’t need to start a project with any layout framework. CSS is the framework. When we have feature queries, progressively enhancing layouts to adopt new layout engines is suddenly not so hard.

\n\n\n
/* Your mobile-first, non-CSS grid styles goes here */\n\n/* The @supports rule below is ignored by browsers that don't\n   support CSS grid, _or_ don't support @supports. */\n@supports (display: grid) {\n  /* Larger screen layout */\n  @media (min-width: 40em) {\n    /* Your progressively enhanced grid layout styles go here */\n  }\n}
\n\n\n

Using JavaScript solutions for layout and presentations problems is not new. It was something we did when we lied to ourselves in 2009 that every website had to look in IE6 exactly as it did in the more capable browsers of that time. If we’re still developing websites to look the same in every browser in 2019, we should reassess our development goals. There will always be some browser we’ll have to support that can’t do everything those modern, evergreen browsers can. Total visual parity on all platforms is not only a pursuit made in vain, it’s the principal foe of progressive enhancement.

\n\n\n

I’m not here to kill JavaScript

\n\n\n

Make no mistake, I have no ill will toward JavaScript. It’s given me a career and—if I’m being honest with myself—a source of enjoyment for over a decade. Like any long-term relationship, I learn more about it the more time I spend with it. It’s a mature, feature-rich language that only gets more capable and elegant with every passing year.

\n\n\n

Yet, there are times when I feel like JavaScript and I are at odds. I am critical of JavaScript. Or maybe more accurately, I’m critical of how we’ve developed a tendency to view it as a first resort to building for the web. As I pick apart yet another bundle not unlike a tangled ball of Christmas tree lights, it’s become clear that the web is drunk on JavaScript. We reach for it for almost everything, even when the occasion doesn’t call for it. Sometimes I wonder how vicious the hangover will be.

\n\n\n

In a series of articles to follow, I’ll be giving more practical advice to follow to stem the encroaching tide of excessive JavaScript and how we can wrangle it so that what we build for the web is usable—or at least more so—for everyone everywhere. Some of the advice will be preventative. Some will be mitigating “hair of the dog” measures. In either case, the outcomes will hopefully be the same. I believe that we all love the web and want to do right by it, but I want us to think about how to make it more resilient and inclusive for all.

\n\n\n


\n\"\"","direction":"ltr"},"alternate":[{"href":"http://feedproxy.google.com/~r/alistapart/main/~3/QhDNVVA5eoQ/","type":"text/html"}],"canonical":[{"href":"https://alistapart.com/article/responsible-javascript-part-1/","type":"text/html"}],"crawled":1569828677486,"published":1569828677486,"origin":{"streamId":"feed/http://feeds.feedburner.com/alistapart/main","title":"A List Apart: The Full Feed","htmlUrl":"https://alistapart.com"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg","width":620,"height":340,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"RFlzskW4NhJjlZfijOSI8IXqM9+zz6V9qnDVl1gxaJs=_16d72eabb28:1d8d:90d684ff","originId":"https://www.raywenderlich.com/5685025-android-fall-sale-giveaway-winners-and-last-day-for-discount","fingerprint":"7e246132","title":"Android Fall Sale Giveaway Winners – and Last Day for Discount! [FREE]","updated":1569591049000,"summary":{"content":"Check out the winners of our Android Fall Sale giveaway — and don’t forget today is the last day to grab the discount on our any of our Android books and limited-time bundles!","direction":"ltr"},"alternate":[{"href":"https://www.raywenderlich.com/5685025-android-fall-sale-giveaway-winners-and-last-day-for-discount","type":"text/html"}],"crawled":1569591049000,"published":1569591049000,"origin":{"streamId":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more","htmlUrl":"http://www.raywenderlich.com/feed"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Miscellaneous"],"originId":"https://nshipster.com/ios-13","recrawled":1569511972636,"updateCount":1,"fingerprint":"a9d09fb8","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:46322:18991ffa","updated":1569222000000,"author":"Mattt","summary":{"direction":"ltr","content":"

To mark last week’s release of iOS 13, we’re taking a look at some obscure (largely undocumented) APIs that you can now use in your apps.

"},"alternate":[{"href":"https://nshipster.com/ios-13/","type":"text/html"}],"crawled":1569418049005,"title":"iOS 13","published":1569222000000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"

Apple announced a lot at WWDC this year.\nDuring the conference and the days that followed,\na lot of us walked around in a daze,\nattempting to recover from have our minds “hashtag-mindblown’d” (#\uD83E\uDD2F).\nBut now a few months later,\nafter everything announced has launched\n(well, almost everything)\nthose very same keynote announcements now elicit far different reactions:

\n
\n

Dark Mode?

\n

\uD83D\uDE0E Already covered it.

\n

SwiftUI?

\n

\uD83D\uDE44 Give it another year or two.

\n

Augmented Reality?

\n

\uD83E\uDD71 Wake us up when Apple announces AR glasses.

\n\n
\n

Although the lion’s share of attention\nhas been showered on the aforementioned features,\nnot nearly enough coverage has been given to the rest of iOS 13 —\nand that’s a shame,\nbecause this release is among the most exciting in terms of new functionality\nand satisfying in terms of improving existing functionality.

\n

So to mark last week’s release of iOS 13,\nwe’re taking a look at some obscure (largely undocumented) APIs\nthat you can now use in your apps.\nWe’ve scavenged the best bits out of the\niOS 13 Release Notes\nAPI diffs,\nand now present them to you.

\n

Here are some of our favorite things you can do starting in iOS 13:

\n
\n

\nGenerate Rich Representations of URLs

\n

New in iOS 13,\nthe\nLinkPresentation framework\nprovides a convenient, built-in way to replicate the\nrich previews of URLs you see in Messages.\nIf your app has any kind of chat or messaging functionality,\nyou’ll definitely want to check this out.

\n

Rich previews of URLs have a rich history\ngoing at least as far back as the early ’00s,\nwith the spread of\nMicroformats\nby semantic web pioneers,\nand early precursors to Digg and Reddit\nusing khtml2png\nto generate thumbnail images of web pages.\nFast forward to 2010,\nwith the rise of social media and user-generated content,\nwhen Facebook created the OpenGraph protocol\nto allow web publishers to customize how their pages looked\nwhen posted on the Newsfeed.

\n

These days,\nmost websites reliably have OpenGraph <meta> tags on their site\nthat provide a summary of their content for\nsocial networks, search engines, and anywhere else that links are trafficked.\nFor example,\nhere’s what you would see if you did “View Source” for this very webpage:

\n
<meta property="og:site_name" content="NSHipster" />\n        <meta property="og:image" content="https://nshipster.com/logo.png" />\n        <meta property="og:type" content="article" />\n        <meta property="og:title" content="iOS 13" />\n        <meta property="og:url" content="https://nshipster.com/ios-13/" />\n        <meta property="og:description" content="To mark last week's release of iOS 13, we're taking a look at some obscure (largely undocumented) APIs that you can now use in your apps." />\n        
\n

If you wanted to consume this information in your app,\nyou can now use the LinkPresentation framework’s LPMetadataProvider class\nto fetch the metadata and optionally construct a representation:

\n
import LinkPresentation\n        let metadataProvider = LPMetadataProvider()\n        let url = URL(string: "https://nshipster.com/ios-13/")!\n        let metadataProvider = LPMetadataProvider()\n        metadataProvider.startFetchingMetadata(for: url) { [weak self] metadata, error in\n        guard let metadata = metadata else { return }\n        let linkView = LPLinkView(metadata: metadata)\n        self?.view.addSubview(linkView)\n        }\n        
\n

After setting appropriate constraints\n(and perhaps a call to sizeToFit()),\nyou’ll get the following,\nwhich the user can tap to preview the linked webpage:

\n\n\n\"iOS\n\n\n

Alternatively,\nif you already have the metadata in-app,\nor otherwise can’t or don’t want to fetch remotely,\nyou can construct an LPLinkMetadata directly:

\n
let metadata = LPLinkMetadata()\n        metadata.url = url\n        metadata.title = "iOS 13"\n        metadata.iconProvider = ...\n        let linkView = LPLinkView(metadata: metadata)\n        
\n\n

\nPerform On-Device Speech Recognition

\n

SFSpeechRecognizer\ngets a major upgrade in iOS 13 —\nmost notably for its added support for on-device speech recognition.

\n

Previously,\ntranscription required an internet connection\nand was restricted to a maximum of 1-minute duration\nwith daily limits for requests.\nBut now,\nyou can do speech recognition completely on-device and offline,\nwith no limitations.\nThe only caveats are that\noffline transcription isn’t as good as what you’d get with a server connection,\nand is only available for certain languages.

\n

To determine whether offline transcription is available for the user’s locale,\ncheck the SFSpeechRecognizer property\nsupportsOnDeviceRecognition.\nAt the time of publication, the list of supported languages are as follows:

\n
\n
\n
English
\n
United States (en-US)
\n
Canada (en-CA)
\n
Great Britain (en-GB)
\n
India (en-IN)
\n
Spanish
\n
United States (es-US)
\n
Mexico (es-MX)
\n
Spain (es-ES)
\n
Italian
\n
Italy (it-IT)
\n
Portuguese
\n
Brazil (pt-BR)
\n
Russian
\n
Russia (ru-RU)
\n
Turkish
\n
Turkey (tr-TR)
\n
Chinese
\n
Mandarin (zh-cmn)
\n
Cantonese (zh-yue)
\n
\n
\n\n

But that’s not all for speech recognition in iOS 13!\nSFSpeechRecognizer now provides information including\nspeaking rate and average pause duration,\nas well as voice analytics features like\njitter (variations in pitch) and\nshimmer (variations in amplitude).

\n
import Speech\n        guard SFSpeechRecognizer.authorizationStatus() == .authorized\n        let recognizer = SFSpeechRecognizer()\n        else {\n        fatalError()\n        }\n        let url: URL = ...\n        let request = SFSpeechURLRecognitionRequest(url: url)\n        recognizer.recognitionTask(with: request) { (result, error) in\n        guard let result = result else { return }\n        for segment in result.bestTranscription.segments {\n        guard let voiceAnalytics = segment.voiceAnalytics else { continue }\n        let pitch = voiceAnalytics.pitch.acousticFeatureValuePerFrame\n        let voicing = voiceAnalytics.voicing.acousticFeatureValuePerFrame\n        let jitter = voiceAnalytics.jitter.acousticFeatureValuePerFrame\n        let shimmer = voiceAnalytics.shimmer.acousticFeatureValuePerFrame\n        }\n        }\n        
\n

Information about pitch and voicing and other features\ncould be used by your app\n(perhaps in coordination with CoreML)\nto differentiate between speakers\nor determine subtext from a speaker’s inflection.

\n

\nSend and Receive Web Socket Messages

\n

Speaking of the Foundation URL Loading System,\nwe now have native support for\nsomething that’s been at the top of our wish list for many years:\nweb sockets.

\n

Thanks to the new\nURLSessionWebSocketTask class\nin iOS 13,\nyou can now incorporate real-time communications in your app\nas easily and reliably as sending HTTP requests —\nall without any third-party library or framework:

\n
let url = URL(string: "wss://...")!\n        let webSocketTask = URLSession.shared.webSocketTask(with: url)\n        webSocketTask.resume()\n        // Send one message\n        let message: URLSessionWebSocketTask.Message = .string("Hello, world!")\n        webSocketTask.send(message) { error in\n        ...\n        }\n        // Receive one message\n        webSocketTask.receive { result in\n        guard case let .success(message) = result else { return }\n        ...\n        }\n        // Eventually...\n        webSocketTask.cancel(with: .goingAway, reason: nil)\n        
\n\n

For years now,\nnetworking has been probably the fastest-moving part\nof the whole Apple technology stack.\nEach WWDC,\nthere’s so much to talk about that\nthat they routinely have to break their content across two separate sessions.\n2019 was no exception,\nand we highly recommend that you take some time to check out this year’s\n“Advances in Networking” sessions\n(Part 1,\nPart 2).

\n

\nDo More With Maps

\n

MapKit is another part of Apple SDKs\nthat’s consistently had a strong showing at WWDC year after year.\nAnd it’s often the small touches that make the most impact in our day-to-day.

\n

For instance,\nthe new\nMKMapView.CameraBoundary API\nin iOS 13\nmakes it much easier to constrain a map’s viewport to a particular region\nwithout locking it down completely.

\n
let region = MKCoordinateRegion(center: mapView.center,\n        latitudinalMeters: 1000,\n        longitudinalMeters: 1000)\n        mapView.cameraBoundary = MKMapView.CameraBoundary(coordinateRegion: region)\n        
\n

And with the new\nMKPointOfInterestFilter API,\nyou can now customize the appearance of map views\nto show only certain kinds of points of interest\n(whereas previously it was an all-or-nothing proposition).

\n
let filter = MKPointOfInterestFilter(including: [.cafe])\n        mapView.pointOfInterestFilter = filter // only show cafés\n        
\n

Finally,\nwith MKGeoJSONDecoder,\nwe now have a built-in way to pull in GeoJSON shapes\nfrom web services and other data sources.

\n
let decoder = MKGeoJSONDecoder()\n        if let url = URL(string: "..."),\n        let data = try? Data(contentsOfURL: url),\n        let geoJSONObjects = try? decoder.decode(data) {\n        for case let overlay as MKOverlay in geoJSONObjects {\n        mapView.addOverlay(overlay)\n        }\n        }\n        
\n

\nKeep Promises in JavaScript

\n

If you enjoyed our article about JavaScriptCore,\nyou’d be thrilled to know that JSValue objects\nnow natively support promises.

\n

For the uninitiated:\nin JavaScript, a Promise\nis an object that represents the eventual completion (or rejection)\nof an asynchronous operation\nand its resulting value.\nPromises are a mainstay of modern JS development —\nperhaps most notably within the fetch API.

\n

Another addition to JavaScriptCore in iOS 13\nis support for symbols\n(no, not those symbols).\nFor more information about\ninit(newSymbolFromDescription:in:),\nrefer to the docs\njust guess how to use it.

\n

\nRespond to Objective-C Associated Objects (?)

\n

On a lark,\nwe decided to see if there was anything new in Objective-C this year\nand were surprised to find out about\nobjc_setHook_setAssociatedObject.\nAgain, we don’t have much to go on except the declaration,\nbut it looks like you can now configure a block to execute when\nan associated object is set.\nFor anyone still deep in the guts of the Objective-C runtime,\nthis sounds like it could be handy.

\n

\nTame Activity Items (?)

\n

On the subject of missing docs:\nUIActivityItemsConfiguration\nseems like a compelling option for managing\nactions in the new iOS 13 share sheets,\nbut we don’t really know where to start…

\n

\"iOS

\n

Shame that we don’t have the information we need to take advantage of this yet.

\n

\nFormat Lists and Relative Times

\n

As discussed in a previous article,\niOS 13 brings two new formatters to Foundation:\nListFormatter\nand\nRelativeDateTimeFormatter.

\n

Not to harp on about this,\nbut both of them are still undocumented,\nso if you want to learn more,\nwe’d recommend checking out that article from July.\nOr, if you’re in a rush\nhere’s a quick example demonstrating how to use both of them together:

\n
import Foundation\n        let relativeDateTimeFormatter = RelativeDateTimeFormatter()\n        relativeDateTimeFormatter.dateTimeStyle = .named\n        let listFormatter = ListFormatter()\n        listFormatter.string(from: [\n        relativeDateTimeFormatter.localizedString(from: DateComponents(day: -1)),\n        relativeDateTimeFormatter.localizedString(from: DateComponents(day: 0)),\n        relativeDateTimeFormatter.localizedString(from: DateComponents(day: 1))\n        ]) // "yesterday, today, and tomorrow"\n        
\n

\nTrack the Progress of Enqueued Operations

\n

Starting in iOS 13,\nOperationQueue now has a\nprogress property.

\n

Granted,\n(NS)Progress\nobjects aren’t the most straightforward or convenient things to work with\n(we’ve been meaning to write an article about them at some point),\nbut they have a complete and well-considered API,\nand even have some convenient slots in app frameworks.

\n

For example,\ncheck out how easy it is to wire up a UIProgressView\nto display the live-updating progress of an operation queue\nby way of its observedProgress property:

\n
import UIKit\n        fileprivate class DownloadOperation: Operation { ... }\n        class ViewController: UIViewController {\n        private let operationQueue = {\n        let queue = OperationQueue()\n        queue.maxConcurrentOperationCount = 1\n        }()\n        @IBOutlet private var progressView: UIProgressView!\n        @IBAction private func startDownloading(_ sender: Any) {\n        operationQueue.cancelAllOperations()\n        progressView.observedProgress = operationQueue.progress\n        for url in [...] {\n        let operation = DownloadOperation(url: url)\n        operationQueue.addOperation(operation)\n        }\n        }\n        }\n        
\n

It’s also worth mentioning a few other APIs coming to in 13,\nlike\nschedule(after:interval:tolerance:options:_:),\nwhich clues OperationQueue into the new\nCombine framework\nin a nice way,\nand addBarrierBlock(_:),\nwhich presumably works like\nDispatch barrier blocks\n(though without documentation, it’s anyone’s guess).

\n

\nManage Background Tasks with Ease

\n

One of the things that often differentiates category-defining apps from their competitors\nis their use of background tasks\nto make sure the app is fully synced and updated for the next time\nit enters the foreground.

\n

iOS 7 was the first release to provide\nan official API for scheduling background tasks\n(though developers had employed various creative approaches prior to this).\nBut in the intervening years,\nmultiple factors —\nfrom an increase in iOS app capabilities and complexity\nto growing emphasis in performance, efficiency, and privacy for apps —\nhave created a need for a more comprehensive solution.

\n

That solution came to iOS 13 by way of the new\nBackgroundTasks framework.

\n

As described in this year’s WWDC session\n“Advances in App Background Execution”,\nthe framework distinguishes between two broad classes of background tasks:

\n
    \n
  • \napp refresh tasks:\nshort-lived tasks that keep an app up-to-date throughout the day
  • \n
  • \nbackground processing tasks:\nlong-lived tasks for performing deferrable maintenance tasks
  • \n
\n

The WWDC session and the accompanying sample code project\ndo a great job of explaining how to incorporate both of these\ninto your app.\nBut if you want the quick gist of it,\nhere’s a small example of an app that schedules periodic refreshes\nfrom a web server:

\n
import UIKit\n        import BackgroundTasks\n        fileprivate let backgroundTaskIdentifier = "com.nshipster.example.task.refresh"\n        @UIApplicationMain\n        class AppDelegate: UIResponder, UIApplicationDelegate {\n        var window: UIWindow?\n        lazy var backgroundURLSession = {\n        let configuration = URLSessionConfiguration.background(withIdentifier: "com.nshipster.url-session.background")\n        configuration.discretionary = true\n        configuration.timeoutIntervalForRequest = 30\n        return URLSession(configuration: configuration, delegate: ..., delegateQueue: ...)\n        }\n        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        ...\n        BGTaskScheduler.shared.register(forTaskWithIdentifier: backgroundTaskIdentifier, using: nil) { task in\n        self.handleAppRefresh(task: task as! BGAppRefreshTask)\n        }\n        return true\n        }\n        func applicationDidEnterBackground(_ application: UIApplication) {\n        scheduleAppRefresh()\n        }\n        func scheduleAppRefresh() {\n        let request = BGAppRefreshTaskRequest(identifier: backgroundTaskIdentifier)\n        request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 10)\n        do {\n        try BGTaskScheduler.shared.submit(request)\n        } catch {\n        print("Couldn't schedule app refresh: \\(error)")\n        }\n        }\n        func handleAppRefresh(task: BGAppRefreshTask) {\n        scheduleAppRefresh()\n        let url: URL = ...\n        var dataTask = backgroundURLSession.dataTask(with: url) { (data, response, error) in\n        ...\n        let success = (200..<300).contains(response?.statusCode)\n        task.setTaskCompleted(success: success)\n        }\n        task.expirationHandler = {\n        dataTask.cancel()\n        }\n        dataTask.resume()\n        }\n        ...\n        }\n        
\n\n

\nAnnotate Text Content Types for Better Accessibility

\n

You know how frustrating it is to hear some people read out URLs?\n(“eɪʧ ti ti pi ˈkoʊlən slæʃ slæʃ ˈdʌbəlju ˈdʌbəlju ˈdʌbəlju dɑt”…)\nThat’s what it can be like when\nVoiceOver\ntries to read something without knowing more about what it’s reading.

\n

iOS 13 promises to improve the situation considerably\nwith the new accessibilityTextualContext property\nand UIAccessibilityTextAttributeContext NSAttributedString attribute key.\nWhenever possible,\nbe sure to annotate views and attributed strings with\nthe constant that best describes the kind of text being displayed:

\n
    \n
  • UIAccessibilityTextualContextConsole
  • \n
  • UIAccessibilityTextualContextFileSystem
  • \n
  • UIAccessibilityTextualContextMessaging
  • \n
  • UIAccessibilityTextualContextNarrative
  • \n
  • UIAccessibilityTextualContextSourceCode
  • \n
  • UIAccessibilityTextualContextSpreadsheet
  • \n
  • UIAccessibilityTextualContextWordProcessing
  • \n
\n\n

\nRemove Implicitly Unwrapped Optionals from View Controllers Initialized from Storyboards

\n

SwiftUI may have signaled the eventual end of Storyboards,\nbut that doesn’t mean that things aren’t and won’t continue to get better\nuntil if / when that day comes.

\n

One of the most irritating anti-patterns for Swift purists\nwhen working on iOS projects with Storyboards\nhas been view controller initialization.\nDue to an impedance mismatch between\nInterface Builder’s “prepare for segues” approach and\nSwift’s object initialization rules,\nwe frequently had to choose between\nmaking all of our properties non-private, variable, and (implicitly unwrapped) optionals,\nor foregoing Storyboards entirely.

\n

Xcode 11 and iOS 13 allow these paradigms to reconcile their differences\nby way of the new @IBSegueAction attribute\nand some new UIStoryboard class methods:

\n

First,\nthe @IBSegueAction attribute\ncan be applied view controller method declarations\nto designate itself as the API responsible for\ncreating a segue’s destination view controller\n(i.e. the destinationViewController property of the segue parameter\nin the prepare(for:sender:) method).

\n
@IBSegueAction\n        func makeProfileViewController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ProfileViewController? {\n        ProfileViewController(\n        coder: coder,\n        name: self.selectedName,\n        avatarImageURL: self.selectedAvatarImageURL\n        )\n        }\n        
\n

Second,\nthe UIStoryboard class methods\ninstantiateInitialViewController(creator:)\nand instantiateViewController(identifier:creator:)\noffer a convenient block-based customization point for\ninstantiating a Storyboard’s view controllers.

\n
import UIKit\n        struct Person { ... }\n        class ProfileViewController: UIViewController {\n        let name: String\n        let avatarImageURL: URL?\n        init?(coder: NSCoder, name: String, avatarImageURL: URL?) {\n        self.name = name\n        self.avatarImageURL = avatarImageURL\n        super.init(coder: coder)\n        }\n        required init?(coder: NSCoder) {\n        fatalError("init(coder:) has not been implemented")\n        }\n        }\n        let storyboard = UIStoryboard(name: "ProfileViewController", bundle: nil)\n        storyboard.instantiateInitialViewController(creator: { decoder in\n        ProfileViewController(\n        coder: decoder,\n        name: "Johnny Appleseed",\n        avatarImageURL: nil\n        )\n        })\n        
\n

Together with the new UIKit Scene APIs,\niOS 13 gives us a lot to work with\nas we wait for SwiftUI to mature and stabilize.

\n\n
\n

That does it for our round-up of iOS 13 features\nthat you may have missed.\nBut rest assured —\nwe’re planning to cover many more new APIs in future NSHipster articles.

\n

If there’s anything we missed that you’d like for us to cover,\nplease @ us on Twitter!

"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Cocoa"],"originId":"https://nshipster.com/apns-device-tokens","fingerprint":"a5ee91d6","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:46321:18991ffa","updated":1568617200000,"author":"Mattt","summary":{"direction":"ltr","content":"

Precedent plays an important role throughout software engineering. So what does a change in iOS 13 to push notification registration have to say about Apple’s API design decisions?

"},"alternate":[{"href":"https://nshipster.com/apns-device-tokens/","type":"text/html"}],"crawled":1569418049005,"title":"Apple Push Notification Device Tokens","published":1568617200000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"

In law,\nthe latin phrase\nstare decisis (“to stand by things decided”)\nis often used to refer to the doctrine of precedent —\nthe idea that,\nwhen deciding a case,\na court should look to previous decisions made\nfor cases with similar facts and scenarios.\nThis principle serves as a foundation of the American legal system,\nand the English common law from which it derives.

\n

For example,\nconsider Apple v. Pepper,\nwhich was argued before the Supreme Court of the United States\nin its most recent session\nand sought to settle the following question:

\n
\n

If Apple and its App Store constitute a monopoly,\ncan consumers sue Apple for offering apps at higher-than-competitive prices,\neven when the prices are set by third-party developers?

\n
\n

In its decision,\nthe Court relied on precedent set in 1977\nby a case known as Illinois Brick,\nwhich itself affirmed a ruling made a decade earlier\nin a case called Hanover Shoe.\nOn its face,\niPhones in 2010’s would seem to have little to do with bricks from the 1970’s\n(aside from the obvious connotation),\nbut within the context of\nUnited States antitrust law,\nthe connection between the two was inescapable.

\n\n

Adherence to precedence confers inertia in the decision-making process.\nIt promotes stability throughout the legal system\nand the institutions that rely on a consistent application of laws.

\n

However,\nlike inertia,\nprecedence can also be overcome with sufficiently compelling reasons;\nwe are bound by the past only insofar as to give it due consideration.

\n
\n

Bearing all of that in mind,\nlet’s smash cut\nto our subject for this week’s brief:\nApple Push Notification Device Tokens —\nand in particular,\na single change in iOS 13 that may incidentally break push notifications\nfor thousands of apps.

\n

\nA Push Notifications Primer

\n

Push notifications allow apps to communicate with users\nin response to remote events when they aren’t currently in use.

\n

Unlike SMS or email,\nwhich allows a sender to communicate with a recipient directly\nusing a unique identifier (a phone number and email address, respectively),\ncommunication between the app’s remote server and the user’s local device\nare facilitated by the Apple Push Notification service\n(APNs).

\n

Here’s how that works:

\n\n

The deviceToken parameter in the app delegate method\nis an opaque Data value —\nkind of like a really long unique phone number or email address —\nthat the app’s push notification provider uses\nto route notifications through APNs to reach\nthis particular installation of the app.

\n

In principle,\nrepresenting this parameter as a Data value makes a lot of sense —\nthe value itself is meaningless.\nHowever, in practice,\nthis API design decision has been the source of untold amounts of heartache.

\n

\nThe Enduring Challenges of Sending Device Tokens Back to the Server

\n

When the app delegate receives its deviceToken,\nthat’s not the end of the story.\nIn order for its to be used to send push notifications,\nit needs to be sent from the client to the server.

\n

The question is, “How”?

\n

Before you jump to a particular answer,\nconsider the historical context of iOS 3 (circa 2009),\nwhen push notifications were first introduced:

\n

\n“Back in My Day…“\n

\n

You could create an NSURLRequest object,\nset its httpBody property to the deviceToken,\nand send it using NSURLConnection,\nbut you’d probably also want to include some additional information —\nlike a username or email address —\nto associate it with an account in the app.\nThat meant that the data you set as a request’s HTTP body\ncouldn’t just be the device token.

\n

Sending an HTTP POST body withapplication/x-www-form-urlencoded\n(e.g. username=jappleseed&deviceToken=____)\nis one possibility for encoding multiple fields into a single payload,\nbut then the question becomes,\n“How do you encode binary data into text?”

\n

Base64\nis a great binary-to-text encoding strategy,\nbut NSData -base64EncodedStringWithOptions:\nwouldn’t be available until iOS 7,\nfour years after push notifications were first introduced in iOS 3.\nWithout CocoaPods or a strong open-source ecosystem\nto fill in the gaps,\nyou were left to follow\nblog posts\ndescribing how to roll your own implementation,\nhoping that things would work as advertised.

\n\n

Given the complexity in using Base64 encoding on iOS < 7,\nmost developers instead opted to take advantage of\nwhat they saw as an easier, built-in alternative:

\n

\nNSData, in its Own Words

\n

Developers,\nin an attempt to understand what exactly\nthis deviceToken parameter was,\nwould most likely have passed it into an NSLog statement:

\n
NSLog(@"%@", deviceToken);\n        // Prints "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"\n        
\n

Unfortunately,\nfor developers less versed in matters of data and encoding,\nthis output from NSLog may have led them astray:
\n“Oh wow, so deviceToken is actually a string!\n(I wonder why Apple was making this so difficult in the first place).\nBut no matter — I can take it from here.”

\n
// ⚠️ Warning: Don't do this\n        NSString *token = [[[[deviceToken description]\n        stringByReplacingOccurrencesOfString:@" " withString:@""]\n        stringByReplacingOccurrencesOfString:@"<" withString:@""]\n        stringByReplacingOccurrencesOfString:@">" withString:@""];\n        
\n

It’s unclear whether push notification service providers spurred this practice\nby requiring Base16 / hexadecimal representations from the beginning,\nor if they adopted it in response to how folks were\nalready accustomed to doing it,\nbut either way,\nthe practice stuck.\nAnd for nearly a decade,\nthis was how a significant percentage of apps were handling\npush notification device token registration.

\n

That was until Swift 3 and iOS 10.

\n\n

\nRelitigating the Past with Swift 3

\n

By 2016,\nSwift had stabilized and matured to the point that\nmost if not many developers were choosing to write new apps in Swift,\nor at least write all new code in Swift for existing apps.

\n

For those who did,\nthe transition to Swift 3\nwas most memorable for its painful migration from Swift 2.\nAs part of “the grand API renaming”\ncommon Foundation types, including NSData,\ndropped their NS prefix in APIs,\nusing a bridged, Swift value type in its place.\nFor the most part,\nthings worked as expected.\nBut there were a few differences in behavior —\nlargely undocumented or incidental behavior\nthat caused a breaking change.\nFor example,\nconsider the following change in\napplication(_:didRegisterForRemoteNotificationsWithDeviceToken:):

\n
// Swift 2: deviceToken is NSData\n        deviceToken.description // "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"\n        // Swift 3: deviceToken is Data\n        deviceToken.description // "32 bytes"\n        
\n

However,\nmany developers remained undeterred by what was seen as a minor annoyance,\nand worked around the issue by recasting to NSData and its former behavior:

\n
// ⚠️ Warning: Don't do this\n        let tokenData = deviceToken as NSData\n        let token = tokenData.description\n        let token = "\\(deviceToken)".replacingOccurrences(of: " ", with: "")\n        .replacingOccurrences(of: "<", with: "")\n        .replacingOccurrences(of: ">", with: "")\n        
\n

Once again,\ndoing things the wrong way\nmanaged to keep things working for another couple years.

\n

But that’s all coming to an end with iOS 13.

\n\n

\nOverturned in iOS 13

\n

iOS 13 changes the format of descriptions\nfor Foundation objects,\nincluding NSData:

\n
// iOS 12\n        (deviceToken as NSData).description // "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"\n        // iOS 13\n        (deviceToken as NSData).description // "{length = 32, bytes = 0x965b251c 6cb1926d e3cb366f dfb16ddd ... 5f857679 376eab7c }"\n        
\n

Whereas previously,\nyou could coerce NSData to spill its entire contents\nby converting it into a String,\nit now reports its length and a truncated summary of its internal bytes.

\n

So from now on,\nif you need to convert your push notification registration deviceToken\ninto a Base16-encoded / hexadecimal string representation,\nyou should do the following:

\n
let deviceTokenString = deviceToken.map { String(format: "%02x", $0) }.joined()\n        
\n

For clarity, let’s break this down and explain each part:

\n
    \n
  • The map method operates on each element of a sequence.\nBecause Data is a sequence of bytes in Swift,\nthe passed closure is evaluated for each byte in deviceToken.
  • \n
  • The String(format:) initializer\nevaluates each byte in the data\n(represented by the anonymous parameter $0)\nusing the %02x format specifier,\nto produce a zero-padded, 2-digit hexadecimal representation of\nthe byte / 8-bit integer.
  • \n
  • After collecting each byte representation created by the map method,\njoined() concatenates each element into a single string.
  • \n
\n\n
\n

Was Apple irresponsible in making this particular change?
\nWe’d argue: No, not really.

\n

Developers shouldn’t have relied on a specific format for\nan object’s description.

\n

Dumping an entire Data value’s contents becomes untenable at a certain point,\nand this change to a more succinct summary\nmakes debugging larger data blobs significantly easier.

\n
\n

Like we said about laws at the start of this article,\nprecedence is a form of inertia,\nnot an immutable truth.

\n

Stare decisis plays an important role\nthroughout software engineering.\nExamples like the “Referer” [sic] header” —\neven the conventions we have about\nthe direction of electricity flow —\ndemonstrate the value of sticking to decisions,\nunless an opportunity arises to compel change.

"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Miscellaneous"],"originId":"https://nshipster.com/dark-mode","fingerprint":"49169576","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:46320:18991ffa","updated":1567407600000,"author":"Mattt","summary":{"direction":"ltr","content":"

After waiting for what felt like an eternity (but was only, like, a year), Dark Mode is now finally coming to the iPhone and iPad iOS 13. But will your app be ready on launch day?

"},"alternate":[{"href":"https://nshipster.com/dark-mode/","type":"text/html"}],"crawled":1569418049005,"title":"Dark Mode on iOS 13","published":1567407600000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"

Today is Labor Day in the United States\n(and Labour Day in Canada),\na day to celebrate the achievement of the workers\nwho organized to bring about fair and safe conditions for employees —\nprotections that serve as the foundation for the modern workplace.

\n\n

Labor Day is also the unofficial end of summer;\nthe long weekend acting as a buffer between\nthe lemonades, sunburns, and trashy romance novels of June and\nthe pumpkin spice lattes, flu shots, and textbooks of September.

\n

However,\nfor the stereotypical tech worker,\nwho likes the heat of summer about as much as\nthe concept of “work/life balance”,\nLabor Day frequently serves as something else entirely:\na wake-up call.\nThat,\nafter a lazy summer of ideation and experimentation,\nit’s once again time to launch new products and ship new features.\nAnd if you’re an app developer specifically,\nyou may know today as,\n“Oh-\uD83E\uDD2C-it’s-September-and-I-still-haven’t-implemented-Dark-Mode-in-my-app” day.

\n

We’re dedicating this week’s article to anyone out there\nwho’s celebrating this esteemed holiday,\nwhether contemporaneously or in the intervening days until iOS 13 goes GM.\nWe hope that a few quick tips can help shed light\nagainst the shade of your looming deadline.

\n
\n

\n

Dark Mode is an appearance preference\nthat tells the system and participating apps to adopt darker color palettes.\nWhereas an app may display dark text on a light background by default,\nit may instead show white text on a dark background.

\n

Last year,\nDark Mode was the killer feature of macOS 10.14 Mojave,\nand its contemporaneous launch with Safari 12\nrippled throughout the World-Wide Web,\ngaining steady adoption among websites\n(like yours truly)\nand\nother browsers.

\n

After waiting for what felt like an eternity\n(but was only, like, a year),\nDark Mode is now finally coming to the iPhone and iPad iOS 13.\nNo longer will we have to tinker with\nDisplay Accommodations\nto limit our light pollution when browsing Reddit late at night.

\n

\nAdopting Dark Mode on iOS

\n

Apple’s done a great job designing a flexible, convenient API\nand providing excellent documentation to go with it.

\n

Of course, the challenge with Apple technologies\nis that short of telling you that “you’re holding it wrong”,\nthey’ll rarely acknowledge the existence of prior art or alternatives,\nlet alone provide a transition document that in any way resembles\nhow everyone was doing things before we got an officially-sanctioned API.\n(Then again, can you really blame them?)

\n

If you were following 100% of Apple’s guidance to the letter,\nyou’d barely have to change a line or code\nto get your app ready for\nnext week’s special event.\nBut most apps are built on solutions we built for ourselves\nto bridge the gaps in the SDK,\nand it may not be clear how to get on the new\nhappy path\nfrom there.

\n

Apple’s guidance for adopting Dark Mode is fantastic for new projects\nbut doesn’t quite hit all of the points you should be aware of\nwhen preparing your existing app for iOS 13.\nSo without further ado,\nhere’s 6 action items for how to get your app ready for Dark Mode.

\n
\n

\n#Cancel Color Literals

\n

In Xcode,\na color literal\nis code with the prefix #colorLiteral\nthat is rendered as a color swatch in the editor.\nFor example,\nthe code #colorLiteral(red: 1, green: 0.67, blue: 0, alpha: 1)\nis rendered in Xcode as .\nColor literals can be drag-and-dropped from\nthe Media Browser in Xcode 10,\nwhich has been consolidated with Snippets into the new Library panel in Xcode 11.

\n

Both color literals and their cousin, image literals,\nwere introduced in support of Xcode Playgrounds.\nBut don’t let their appearance fool you:\nneither have a place in your app’s codebase.

\n\n

Dark Mode or not,\nyou can replace all usage of color literals throughout your codebase\nfrom the command line:

\n
$ find . -name '*.swift'  \\\n        -exec sed -i '' -E 's/#colorLiteral\\(red: (.*), green: (.*), blue: (.*), alpha: (.*)\\)/UIColor(red: \\1, green: \\2, blue: \\3, alpha: \\4)/ {} \\;\n
\n

But before you do,\nyou might as well do one better\nand replace it with something that will stand the test of time.

\n

\nNix UIColor Hexadecimal Initializers

\n

Among the most common extensions you’ll find in a\nSwiss Army Knife-style CocoaPod\nis a category on UIColor that initializes from a hexadecimal string.\nSomething along the lines of this:

\n
import SwiftySwiftColorSwift\n        let orange = UIColor(hex: "#FB8C00") // \uD83D\uDC4E\n        
\n

Setting aside any qualms about how they’re typically employed and implemented,\nyou’d be well-advised to have these go the way of color literals\nfor the same reasons we described in the previous section.

\n

But worry not!\nYou’ll still have a way to define colors\nusing those hex codes that your designer sent over,\nas we’ll see in our discussion of named colors.

\n

\nFind & Replace Fixed Colors

\n

UIColor defines several class properties\nthat return colors by their common name.\nThese properties are problematic in iOS 13,\nbecause they don’t automatically adjust for light or dark appearance.\nFor example,\nsetting a label’s color to .black looks fine\nagainst the default UITableViewCell background,\nbut it’s illegible when that background becomes black\nwhen Dark Mode is enabled.

\n

To make your app ready for Dark Mode on iOS 13,\nyou’ll most likely want to replace any instance of the following\nUIColor class properties:

\n
\n
    \n
  • red
  • \n
  • orange
  • \n
  • yellow
  • \n
  • brown
  • \n
  • green
  • \n
  • cyan
  • \n
  • blue
  • \n
  • purple
  • \n
  • magenta
  • \n
\n


\n
    \n
  • white
  • \n
  • lightGray
  • \n
  • gray
  • \n
  • darkGray
  • \n
  • black
  • \n
\n
\n

Hopefully you aren’t using the built-in\nROYGBIV\nUIColor constants for much other than occasional layout debugging,\nbut chances you’ll probably find a few instances of .black or .white\npeppered throughout your codebase somewhere.

\n

In any case,\nthe easiest change to support Dark Mode would be to\nreplace any of the aforementioned fixed color properties with\nthe corresponding system-prefixed adaptable color below:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NameAPILightDark
DefaultAccessibleDefaultAccessible
RedsystemRed
OrangesystemOrange
YellowsystemYellow
GreensystemGreen
TealsystemTeal
BluesystemBlue
IndigosystemIndigo
PurplesystemPurple
PinksystemPink
GraysystemGray
Gray (2)systemGray2
Gray (3)systemGray3
Gray (4)systemGray4
Gray (5)systemGray5
Gray (6)systemGray6
\n

You may notice that this table doesn’t provide direct correspondences for\nblack or white (or brown, but disregard that for now).

\n

Black and white don’t have adaptive colors\nbecause their names would cease to be descriptive in Dark Mode;\nif .systemBlack existed, it’d pretty much have to be .white\nto be visible in a dark color pallet.

\n

Which gets to a deeper point about color management in an era of Dark Mode…

\n

\nUse Semantic Colors

\n

The best way to ensure consistent rendering of your UI\non any device in any appearance mode\nis to use semantic colors,\nnamed according to their function rather than appearance.

\n

Similar to how Dynamic Type\nuses semantic tags like “Headline” and “Body” to\nautomatically set the most suitable font for the user’s display preferences,\nsemantic colors —\nor what Apple’s calling\nUI Element Colors —\nprovide future-proof behavior for your views and controls.

\n

When styling your component,\nset the color to the closest UIColor class property below:

\n
\n
\n
Label Colors
\n
label
\n
secondaryLabel
\n
tertiaryLabel
\n
quaternaryLabel
\n
Text Colors
\n
placeholderText
\n
Link Colors
\n
link
\n
Separator Colors
\n
separator
\n
opaqueSeparator
\n
Fill Colors
\n
systemFill
\n
secondarySystemFill
\n
tertiarySystemFill
\n
quaternarySystemFill
\n
Background Colors
\n
systemBackground
\n
secondarySystemBackground
\n
tertiarySystemBackground
\n
Grouped Background Colors
\n
systemGroupedBackground
\n
secondarySystemGroupedBackground
\n
tertiarySystemGroupedBackground
\n
\n
\n

\nUpgrade Homegrown Semantic Color Palettes

\n

If you’ve given any thought to color management in your app\nyou’ll have likely landed on some form of the following strategy,\nwhereby you define semantic colors according to fixed colors\nwithin a namespace or in an extension to UIColor.

\n

For example,\nthe following example shows how an app might define\nUIColor constants from the\nMaterial UI color system\nand reference them from semantic UIColor class properties:

\n
import UIKit\n        extension UIColor {\n        static var customAccent: UIColor { return MaterialUI.red500 }\n        ...\n        }\n        fileprivate enum MaterialUI {\n        static let orange600 = UIColor(red:   0xFB / 0xFF,\n        green: 0x8C / 0xFF,\n        blue:  0x00 / 0xFF,\n        alpha: 1) // #FB8C00\n        ...\n        }\n        
\n

If your app uses a pattern like this,\nyou can make it Dark Mode compatible\nusing the new\ninit(dynamicProvider:)\nUIColor initializer in iOS 13 like so:

\n
import UIKit\n        extension UIColor\n        static var customAccent: UIColor {\n        if #available(iOS 13, *) {\n        return UIColor { (traitCollection: UITraitCollection) -> UIColor in\n        if traitCollection.userInterfaceStyle == .dark {\n        return MaterialUI.orange300\n        } else {\n        return MaterialUI.orange600\n        }\n        }\n        } else {\n        return MaterialUI.orange600\n        }\n        }\n        
\n

Nothing about the fixed Material UI color constants has to change\nwith this approach.\nInstead, the semantic color property customAccent provides a dynamic color\nthat uses the color most appropriate for the current rendering context.\nWhen Dark Mode is enabled,\na lighter orange is used to contrast against the darker color palette;\notherwise, the behavior is unchanged from the original implementation.

\n

The extra #available check creates some bloat in the implementation,\nbut it’s a small price to pay for the flexibility this approach provides.

\n\n

Unfortunately,\nthere’s one crucial shortcoming to using color properties in this way:\nthey can’t be referenced from Interface Builder.

\n

If your app uses either Storyboards or XIBs,\nthe best approach is to use color assets.

\n

\nManage Colors with an Asset Catalog

\n

Color assets let you manage colors in an Xcode Asset Catalog\nin the same way that you do for\nimages, data, or other resources.

\n\n\n\"Asset\n\n

To create a color set,\nopen an Asset Catalog in your Xcode project,\nclick the + button on the bottom left-hand corner,\nand select “New Color Set”.\nIn the Attributes inspector,\nselect “Any, Dark” appearance.\nColors are frequently expressed in the form (“#RRGGBB”);\nyou can enter colors in this form by\nselecting “8-bit Hexadecimal” from the “Input Method” drop-down.

\n\n\n\"Asset\n\n

Here,\nwe’ve done the same as before,\nexcept instead of defining fixed UIColor constants like orange300 in code,\nwe set those in the color set itself.\nNow when it comes time to reference the color asset\nby the existing semantic class property,\nwe can use the UIColor named initializer:

\n
extension UIColor {\n        @available(iOS 11, *)\n        var customAccent: UIColor! {\n        return UIColor(named: "Accent")\n        }\n        }\n        
\n\n

Your opinion about color assets will largely be informed by\nyour preference or dispreference towards specialized Xcode document editors.\nSome folks like to have everything spelled out in code,\nwhile others appreciate the affordances provided by a bespoke UI\nlike the one provided by Xcode for color sets.

\n

In fact, your opinion of color assets\nis probably concomitant with your feelings about Storyboards —\nwhich is convenient,\nbecause the killer feature of color assets is that\nthey can be referenced from within Interface Builder.\n(If you’re not on team IB, then you can safely skip this whole discussion.)

\n

\nReplace Instances of “Custom Color” in XIBs and Storyboards

\n

The “Custom Color” option in Interface Builder\nthat brings up the macOS system-native color picker\nsuffers the same problem as the color literals and fixed colors\nwe talked about earlier.\nIf you want your XIB-powered views to look good on iOS 13,\nyou’ll need to migrate to named colors.

\n

This can be done easily:\nselect any component in your scene,\nand you can set its color attribute using the same, named color\ndefined in your Asset Catalog.

\n\n\n\"Interface\n\n

For a small project,\nthis can be done by hand for all your screens in under an hour.\nHowever,\nfor a larger app,\nthis is a process you’ll want to automate.

\n

\nXIB Anatomy

\n

Under the hood,\nXIB and Storyboard files are merely XML files like any other:

\n
<?xml version="1.0" encoding="UTF-8"?>\n        <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" <#...#>>\n        <device id="retina6_1" orientation="portrait">\n        <adaptation id="fullscreen"/>\n        </device>\n        <dependencies>\n        <deployment identifier="iOS"/>\n        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>\n        <capability name="Safe area layout guides" minToolsVersion="9.0"/>\n        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>\n        </dependencies>\n        <scenes>\n        <!--View Controller-->\n        <scene sceneID="nsh-ips-ter">\n        <objects>\n        <viewController id="dar-kmo-de1" customClass="ViewController" customModule="NSHipster" customModuleProvider="target" sceneMemberID="viewController">\n        <view key="view" contentMode="scaleToFill" id="mai-nv-iew">\n        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>\n        <color key="backgroundColor" red="1" green="0.69019607843137254" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>\n        </view>\n        </viewController>\n        <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>\n        </objects>\n        </scene>\n        </scenes>\n        </document>\n        
\n

We wouldn’t want to write this from scratch by hand,\nbut there’s nothing too mysterious going on here.

\n

So consider what happens when you use Xcode\nto switch the custom background color of the main view to a named color:

\n
<?xml version="1.0" encoding="UTF-8"?>\n        <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" <#...#>>\n        <device <#...#>/>\n        <dependencies>\n        <#...#>\n        <capability name="Named colors" minToolsVersion="9.0"/> <!-- ❶ -->\n        </dependencies>\n        <scenes>\n        <!-- scenes.scene.objects.viewController.view -->\n        <#...#>\n        <view key="view" contentMode="scaleToFill" id="mai-nv-iew">\n        <#...#>\n        <color key="backgroundColor" name="Accent"/> <!-- ❷ -->\n        </view>\n        </scenes>\n        <resources>\n        <namedColor name="Accent"> <!-- ❸ -->\n        <color red="1" green="0.69019607843137254" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>\n        </namedColor>\n        </resources>\n        </document>\n        
\n
\n
\n
A new "Named colors" capability is added as a dependency\nfor opening the document\n(Xcode uses this to determine whether it can edit files\ncreated by newer versions).
\n
\n
The red, green, blue, and alpha components on the color element\nwere replaced by a single name attribute.
\n
\n
A corresponding namedColor element was added to the top-level resources element.
\n
\n

Based on this understanding,\nwe should know enough to make this change en masse\nwith our own tooling!

\n

\nFinding All Custom Colors

\n\n

The first order of business when migrating your Storyboards and XIBs for Dark Mode\nis to find all of the instances of custom colors.\nYou could go through each by hand and click each of the visual elements…\nor you could run the following command:

\n
$ find . -name '*.xib' -or -name '*.storyboard' \\\n        -exec echo {} \\;        \\\n        -exec xmlstarlet sel -t \\\n        -m "//color[@colorSpace='custom']" -c . -n  {} \\;\n        \nMain.storyboard\n<color red="1" green="0.69019607843137254" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>\n        
\n

This command prints the name of each file\nfollowed by each custom, unnamed color element it found\n(denoted by the colorSpace="custom" attribute).

\n

The resulting list serves as a battle plan for the next phase of attack:

\n

\nFinding Each Distinct Custom Color

\n

Apps tend to reuse a small set of colors\nacross their views — as they should!\nBy running the following command,\nyou can generate a sorted, unique’d list of\nevery custom color in every XIB or Storyboard:

\n
$ find . -name '*.xib' -or -name '*.storyboard' \\\n        -exec xmlstarlet sel -t \\\n        -m "//color[@colorSpace='custom']"  \\\n        -v "concat( @red,' ',@green,' ',@blue,' ',@alpha)" -n  {} \\; \\\n        | sort -u\n        \n1 0.69019607839999997 0.0 1\n
\n

Some entries may be equivalent, within a small delta of each other\n(because, you know… floating-point numbers).\nTo account for account for this,\nand to transform our output into something easier to work with,\nyou can write the output to a file and process it with\na Ruby script like this one:

\n
colors = File.readlines('distinct_colors.txt').map do |line|\n        components = line.strip.split(/\\s+/).flat_map(&:to_f)\n        red, green, blue = components[0..2].map{ |c| (c * 255).floor }\n        alpha = (components.last * 100).floor\n        [red, green, blue, alpha]\n        end\n        colors.uniq.each do |color|\n        puts "#%02X%02X%02X (%d%%)" % color\n        end\n        
\n

From here,\nthe final step is to map each set of\nRGBA values\nto the corresponding named color that you want to replace it with.

\n\n

\nReplacing Custom Colors

\n

At this point,\nwe’re well beyond the point where shell one-liners seem like a good idea.\nSo here’s a Ruby script we wrote up\nthat makes all of the changes we understand to take place\nwhen replacing a custom color with a named color in Interface Builder:

\n\n
require 'nokogiri'\n        def name_for_rgba_components(red, green, blue, alpha)\n        case format('#%02X%02X%02X%02X', red, green, blue, alpha)\n        # Return named color matching RGBA components\n        # e.g. "#F8CB00FF" => "Accent"\n        end\n        end\n        def name_for_white_and_alpha_components(white, alpha)\n        # Return named color matching white and alpha components\n        # e.g. 0.9 => "Off-White"\n        end\n        # Process each Storyboard and XIB file\n        Dir['**/*.{storyboard,xib}'].each do |xib|\n        doc = Nokogiri::XML(File.read(xib))\n        names = []\n        # Collect each custom color and assign it a name\n        doc.xpath('//objects//color').each do |color|\n        next if color['name']\n        name = nil\n        color_space = color['colorSpace']\n        color_space = color['customColorSpace'] if color_space == 'custom'\n        case color_space\n        when 'sRGB', 'calibratedRGB'\n        components = color.attributes\n        .values_at('red', 'green', 'blue', 'alpha')\n        .map(&:value)\n        .map(&:to_f)\n        .map { |c| c * 255 }\n        name = name_for_rgba_components(*components)\n        when 'genericGamma22GrayColorSpace', 'calibratedWhite'\n        components = color.attributes\n        .values_at('white', 'alpha')\n        .map(&:value)\n        .map(&:to_f)\n        name = name_for_white_and_alpha_components(*components)\n        end\n        next unless name\n        named_color = doc.create_element('color',\n        key: color['key'],\n        name: name)\n        color.replace(named_color)\n        names << name\n        end\n        # Proceed to the next file if no named colors were found\n        next if names.empty?\n        # Add the named color capability as a document dependency\n        dependencies = doc.at('/document/dependencies') ||\n        doc.root.add_child(doc.create_element('dependencies'))\n        unless dependencies.at("capability[@name='Named colors']")\n        dependencies << doc.create_element('capability',\n        name: 'Named colors',\n        minToolsVersion: '9.0')\n        end\n        # Add each named color to the document resources\n        resources = doc.at('/document/resources') ||\n        doc.root.add_child(doc.create_element('resources'))\n        names.uniq.sort.each do |name|\n        next if resources.at("namedColor[@name='#{name}']")\n        resources << doc.create_element('namedColor', name: name)\n        end\n        # Save the changes\n        File.write(xib, doc.to_xml(indent: 4, encoding: 'UTF-8'))\n        end\n        
\n

*Phew!*

\n

If you’ve been facing down a deadline for Dark Mode\nat the expense of enjoying one last hurrah of summer,\nwe hope that this article was able to get you out of the office today.

\n

Its ironic that so many of us\nare eschewing our holiday weekend in a scramble to get our apps ready\nfor the annual\nNMOS\nGM.\nBut if it’s any consolation,\nknow that Apple engineers rarely get to observe\nMemorial Day —\nthe unofficial start of summer in America —\nin the run-up to WWDC.

"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"keywords":["Swift"],"originId":"https://nshipster.com/identifiable","fingerprint":"ecd98718","id":"08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16d689af5ed:4631f:18991ffa","updated":1566802800000,"author":"Mattt","summary":{"direction":"ltr","content":"

Swift 5.1 gives us yet another occasion to ponder ontological questions and weigh in the relative merits of various built-in types as stable identifiers.

"},"alternate":[{"href":"https://nshipster.com/identifiable/","type":"text/html"}],"crawled":1569418049005,"title":"Identifiable","published":1566802800000,"origin":{"streamId":"feed/http://nshipster.com/feed.xml","htmlUrl":"https://nshipster.com/","title":"NSHipster"},"content":{"direction":"ltr","content":"
\n

What constitutes the identity of an object?

\n
\n

Philosophers have contemplated such matters throughout the ages.\nWhether it’s to do with\nreconstructed seafaring vessels from antiquity\nor spacefaring vessels from science fiction,\nquestions of Ontology reveal our perception and judgment to be\nmuch less certain than we’d like to believe.

\n

Our humble publication has frequented this topic with some regularity,\nwhether it was attempting to make sense of\nequality in Objective-C\nor appreciating the much clearer semantics of Swift\nvis-à-vis the Equatable protocol.

\n

Swift 5.1 gives us yet another occasion to ponder this old chestnut\nby virtue of the new Identifiable protocol.\nWe’ll discuss the noumenon of this phenomenal addition to the standard library,\nand help you identify opportunities to\nrealize its potential in your own projects.

\n

But let’s dispense with the navel gazing and\njump right into some substance:

\n
\n

Swift 5.1 adds the Identifiable protocol to the standard library,\ndeclared as follows:

\n
protocol Identifiable {\n        associatedtype ID: Hashable\n        var id: ID { get }\n        }\n        
\n

Values of types adopting the Identifiable protocol\nprovide a stable identifier for the entities they represent.

\n

For example,\na Parcel object may use the id property requirement\nto track the package en route to its final destination.\nNo matter where the package goes,\nit can always be looked up by its id:

\n
import CoreLocation\n        struct Parcel: Identifiable {\n        let id: String\n        var location: CLPlacemark?\n        }\n        
\n\n

The Swift Evolution proposal for Identifiable,\nSE-0261,\nwas kept small and focused in order to be incorporated quickly.\nSo, if you were to ask,\n“What do you actually get by conforming to Identifiable?”,\nthe answer right now is “Not much.”\nAs mentioned in the future directions,\nconformance to Identifiable has the potential to unlock\nsimpler and/or more optimized versions of other functionality,\nsuch as the new ordered collection diffing APIs.

\n

But the question remains:\n“Why bother conforming to Identifiable?”

\n

The functionality you get from adopting Identifiable is primarily semantic,\nand require some more explanation.\nIt’s sort of like asking,\n“Why bother conforming to Equatable?”

\n

And actually, that’s not a bad place to start.\nLet’s talk first about Equatable and its relation to Identifiable:

\n

\nIdentifiable vs. Equatable

\n

Identifiable distinguishes the identity of an entity from its state.

\n

A parcel from our previous example\nwill change locations frequently as it travels to its recipient.\nYet a normal equality check (==)\nwould fail the moment it leaves its sender:

\n
extension Parcel: Equatable {}\n        var specialDelivery = Parcel(id: "123456789012")\n        specialDelivery.location = CLPlacemark(\n        location: CLLocation(latitude: 37.3327,\n        longitude: -122.0053),\n        name: "Cupertino, CA"\n        )\n        specialDelivery == Parcel(id: "123456789012") // false\n        specialDelivery.id == Parcel(id: "123456789012").id // true\n        
\n

While this is an expected outcome from a small, contrived example,\nthe very same behavior can lead to confusing results further down the stack,\nwhere you’re not as clear about how different parts work with one another.

\n
var trackedPackages: Set<Parcel> = ...\n        trackedPackages.contains(Parcel(id: "123456789012")) // false (?)\n        
\n

On the subject of Set,\nlet’s take a moment to talk about the Hashable protocol.

\n

\nIdentifiable vs. Hashable

\n

In our article about Hashable,\nwe described how Set and Dictionary use a calculated hash value\nto provide constant-time (O(1)) access to elements in a collection.\nAlthough the hash value used to bucket collection elements\nmay bear a passing resemblance to identifiers,\nHashable and Identifiable have some important distinctions\nin their underlying semantics:

\n
    \n
  • Unlike identifiers,\nhash values are typically state-dependent,\nchanging when an object is mutated.
  • \n
  • Identifiers are stable across launches,\nwhereas hash values are calculated by randomly generated hash seeds,\nmaking them unstable between launches.
  • \n
  • Identifiers are unique,\nwhereas hash values may collide,\nrequiring additional equality checks when fetched from a collection.
  • \n
  • Identifiers can be meaningful,\nwhereas hash values are chaotic\nby virtue of their hashing functions.
  • \n
\n

In short,\nhash values are similar to\nbut no replacement for identifiers.

\n

So what makes for a good identifier, anyway?

\n

\nChoosing ID Types

\n

Aside from conforming to Hashable,\nIdentifiable doesn’t make any other demands of\nits associated ID type requirement.\nSo what are some good candidates?

\n

If you’re limited to only what’s available in the Swift standard library,\nyour best options are Int and String.\nInclude Foundation,\nand you expand your options with UUID and URL.\nEach has its own strengths and weaknesses as identifiers,\nand can be more or less suited to a particular situation:

\n

\nInt as ID

\n

The great thing about using integers as identifiers\nis that (at least on 64-bit systems),\nyou’re unlikely to run out of them anytime soon.

\n

Most systems that use integers to identify records\nassign them in an auto-incrementing manner,\nsuch that each new ID is 1 more than the last one.\nHere’s a simple example of how you can do this in Swift:

\n
struct Widget: Identifiable {\n        private static var idSequence = sequence(first: 1, next: {$0 + 1})\n        let id: Int\n        init?() {\n        guard let id = Widget.idSequence.next() else { return nil}\n        self.id = id\n        }\n        }\n        Widget()?.id // 1\n        Widget()?.id // 2\n        Widget()?.id // 3\n        
\n

If you wanted to guarantee uniqueness across launches,\nyou might instead initialize the sequence with a value\nread from a persistent store like UserDefaults.\nAnd if you found yourself using this pattern extensively,\nyou might consider factoring everything into a self-contained\nproperty wrapper.

\n

Monotonically increasing sequences have a lot of benefits,\nand they’re easy to implement.

\n

This kind of approach can provide unique identifiers for records,\nbut only within the scope of the device on which the program is being run\n(and even then, we’re glossing over a lot with respect to concurrency\nand shared mutable state).

\n

If you want to ensure that an identifier is unique across\nevery device that’s running your app, then\ncongratulations —you’ve hit\na fundamental problem in computer science.\nBut before you start in on\nvector clocks and\nconsensus algorithms,\nyou’ll be relieved to know that there’s a\nmuch simpler solution:\nUUIDs.

\n\n

\nUUID as ID

\n

UUIDs, or\nuniversally unique identifiers,\n(mostly) sidestep the problem of consensus with probability.\nEach UUID stores 128 bits —\nminus 6 or 7 format bits, depending on the\nversion —\nwhich, when randomly generated,\nmake the chances of collision,\nor two UUIDs being generated with the same value,\nastronomically small.

\n

As discussed in a previous article,\nFoundation provides a built-in implementation of (version-4) UUIDs\nby way of the\nUUID type.\nThus making adoption to Identifiable with UUIDs trivial:

\n
import Foundation\n        struct Gadget: Identifiable {\n        let id = UUID()\n        }\n        Gadget().id // 584FB4BA-0C1D-4107-9EE5-C555501F2077\n        Gadget().id // C9FECDCC-37B3-4AEE-A514-64F9F53E74BA\n        
\n

Beyond minor ergonomic and cosmetic issues,\nUUID serves as an excellent alternative to Int\nfor generated identifiers.

\n

However,\nyour model may already be uniquely identified by a value,\nthereby obviating the need to generate a new one.\nUnder such circumstances,\nthat value is likely to be a String.

\n\n

\nString as ID

\n

We use strings as identifiers all the time,\nwhether it takes the form of a username or a checksum or a translation key\nor something else entirely.

\n

The main drawback to this approach is that,\nthanks to The Unicode® Standard,\nstrings encode thousands of years of written human communication.\nSo you’ll need a strategy for handling identifiers like\n“⽜”, “\uD800\uDC8C”, “”, and “\uD83D\uDC2E”\n…and that’s to say nothing of the more pedestrian concerns,\nlike leading and trailing whitespace and case-sensitivity!

\n

Normalization is the key to successfully using strings as identifiers.\nThe easiest place to do this is in the initializer,\nbut, again, if you find yourself repeating this code over and over,\nproperty wrappers can help you here, too.

\n
import Foundation\n        fileprivate extension String {\n        var nonEmpty: String? { isEmpty ? nil : self }\n        }\n        struct Whosit: Identifiable {\n        let id: String\n        init?(id: String) {\n        guard let id = id.trimmingCharacters(in: CharacterSet.letters.inverted)\n        .lowercased()\n        .nonEmpty\n        else {\n        return nil\n        }\n        self.id = id\n        }\n        }\n        Whosit(id: "Cow")?.id // cow\n        Whosit(id: "--- cow ---")?.id // cow\n        Whosit(id: "\uD83D\uDC2E") // nil\n        
\n

\nURL as ID

\n

URLs (or URIs if you want to be pedantic)\nare arguably the most ubiquitous kind of identifier\namong all of the ones described in this article.\nEvery day, billions of people around the world use URLs\nas a way to point to a particular part of the internet.\nSo URLs a natural choice for an id value\nif your models already include them.

\n

URLs look like strings,\nbut they use syntax\nto encode multiple components,\nlike scheme, authority, path, query, and fragment.\nAlthough these formatting rules dispense with much of the invalid input\nyou might otherwise have to consider for strings,\nthey still share many of their complexities —\nwith a few new ones, just for fun.

\n

The essential problem is that\nequivalent URLs may not be equal.\nIntrinsic, syntactic details like\ncase sensitivity,\nthe presence or absence of a trailing slash (/),\nand the order of query components\nall affect equality comparison.\nSo do extrinsic, semantic concerns like\na server’s policy to upgrade http to https,\nredirect from www to the apex domain,\nor replace an IP address with a\nwhich might cause different URLs to resolve to the same webpage.

\n
URL(string: "https://nshipster.com/?a=1&b=2")! ==\n        URL(string: "http://www.NSHipster.com?b=2&a=1")! // false\n        try! Data(contentsOf: URL(string: "https://nshipster.com?a=1&b=2")!) ==\n        Data(contentsOf: URL(string: "http://www.NSHipster.com?b=2&a=1")!) // true\n        
\n\n

If your model gets identifier URLs for records from a trusted source,\nthen you may take URL equality as an article of faith;\nif you regard the server as the ultimate source of truth,\nit’s often best to follow their lead.

\n

But if you’re working with URLs in any other capacity,\nyou’ll want to employ some combination of\nURL normalizations\nbefore using them as an identifier.

\n

Unfortunately, the Foundation framework doesn’t provide\na single, suitable API for URL canonicalization,\nbut URL and URLComponents provide enough on their own\nto let you roll your own\n(though we’ll leave that as an exercise for the reader):

\n
import Foundation\n        fileprivate extension URL {\n        var normalizedString: String { ... }\n        }\n        struct Whatsit: Identifiable {\n        let url: URL\n        var id: { url.normalizedString }\n        }\n        Whatsit(url: "https://example.com/123").id // example.com/123\n        Whatsit(id: "http://Example.com/123/").id // example.com/123\n        
\n

\nCreating Custom Identifier ID Types

\n

UUID and URL both look like strings,\nbut they use syntax rules to encode information in a structured way.\nAnd depending on your app’s particular domain,\nyou may find other structured data types that\nwould make for a suitable identifier.

\n

Thanks to the flexible design of the Identifiable protocol,\nthere’s nothing to stop you from implementing your own ID type.

\n

For example,\nif you’re working in a retail space,\nyou might create or repurpose an existing\nUPC type\nto serve as an identifier:

\n
struct UPC: Hashable {\n        var digits: String\n        implementation details\n        }\n        struct Product: Identifiable {\n        let id: UPC\n        var name: String\n        var price: Decimal\n        }\n        
\n

\nThree Forms of ID Requirements

\n

As Identifiable makes its way into codebases,\nyou’re likely to see it used in one of three different ways:

\n

The newer the code,\nthe more likely it will be for id to be a stored property —\nmost often this will be declared as a constant (that is, with let):

\n
import Foundation\n        // Style 1: id requirement fulfilled by stored property\n        struct Product: Identifiable {\n        let id: UUID\n        }\n        
\n

Older code that adopts Identifiable,\nby contrast,\nwill most likely satisfy the id requirement\nwith a computed property\nthat returns an existing value to serve as a stable identifier.\nIn this way,\nconformance to the new protocol is purely additive,\nand can be done in an extension:

\n
import Foundation\n        struct Product {\n        var uuid: UUID\n        }\n        // Style 2: id requirement fulfilled by computed property\n        extension Product: Identifiable {\n        var id { uuid }\n        }\n        
\n

If by coincidence the existing class or structure already has an id property,\nit can add conformance by simply declaring it in an extension\n(assuming that the property type conforms to Hashable).

\n
import Foundation\n        struct Product {\n        var id: UUID\n        }\n        // Style 3: id requirement fulfilled by existing property\n        extension Product: Identifiable {}\n        
\n

No matter which way you choose,\nyou should find adopting Identifiable in a new or existing codebase\nto be straightforward and noninvasive.

\n
\n

As we’ve said time and again,\noften it’s the smallest additions to the language and standard library\nthat have the biggest impact on how we write code.\n(This speaks to the thoughtful,\nprotocol-oriented\ndesign of Swift’s standard library.)

\n

Because what Identifiable does is kind of amazing:\nit extends reference semantics to value types.

\n

When you think about it,\nreference types and value types differ not in what information they encode,\nbut rather how we treat them.

\n

For reference types,\nthe stable identifier is the address in memory\nin which the object resides.\nThis fact can be plainly observed\nby the default protocol implementation of id for AnyObject types:

\n
extension Identifiable where Self: AnyObject {\n        var id: ObjectIdentifier {\n        return ObjectIdentifier(self)\n        }\n        }\n        
\n

Ever since Swift first came onto the scene,\nthe popular fashion has been to eschew all reference types for value types.\nAnd this neophilic tendency has only intensified\nwith the announcement of SwiftUI.\nBut taking such a hard-line approach makes a value judgment\nof something better understood to be a difference in outlook.

\n

It’s no coincidence that much of the terminology of programming\nis shared by mathematics and philosophy.\nAs developers, our work is to construct logical universes, after all.\nAnd in doing so,\nwe’re regularly tasked with reconciling our own mental models\nagainst that of every other abstraction we encounter down the stack —\ndown to the very way that we understand electricity and magnetism to work.

"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"RFlzskW4NhJjlZfijOSI8IXqM9+zz6V9qnDVl1gxaJs=_16d68670508:1d8c:90d684ff","originId":"https://www.raywenderlich.com/9527-functional-programming-with-kotlin-and-arrow-getting-started","fingerprint":"5064fed4","title":"Functional Programming with Kotlin and Arrow: Getting Started [FREE]","updated":1569414645000,"summary":{"content":"In this tutorial, you will learn the fundamentals of functional programming and how various Kotlin language features enable functional programming concepts.","direction":"ltr"},"alternate":[{"href":"https://www.raywenderlich.com/9527-functional-programming-with-kotlin-and-arrow-getting-started","type":"text/html"}],"crawled":1569414645000,"published":1569414645000,"origin":{"streamId":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more","htmlUrl":"http://www.raywenderlich.com/feed"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]},{"id":"RFlzskW4NhJjlZfijOSI8IXqM9+zz6V9qnDVl1gxaJs=_16d635b7080:1d8b:90d684ff","originId":"https://www.raywenderlich.com/5539282-programming-in-swift-fundamentals","fingerprint":"910498d4","title":"Programming in Swift: Fundamentals [SUBSCRIBER]","updated":1569330000000,"summary":{"content":"Learn about Apple’s open source programming language, Swift, through hands-on examples! Take a deep dive into the Swift language, learning about core Swift concepts like loops, collections, types, optionals, functions, classes, and more.","direction":"ltr"},"alternate":[{"href":"https://www.raywenderlich.com/5539282-programming-in-swift-fundamentals","type":"text/html"}],"crawled":1569330000000,"published":1569330000000,"origin":{"streamId":"feed/http://www.raywenderlich.com/feed","title":"Ray Wenderlich | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more","htmlUrl":"http://www.raywenderlich.com/feed"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5","label":"Programming"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/uncategorized_initial.json b/Frameworks/Account/AccountTests/Feedly/Initial/uncategorized_initial.json new file mode 100644 index 000000000..017803b50 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/uncategorized_initial.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","updated":1569829929101,"continuation":"16d8125cccf:1a5d:90d684ff","items":[{"keywords":["Philosophy"],"originId":"https://www.natashatherobot.com/?p=6640","fingerprint":"ebbe9c9f","id":"/3LYgCDhmnhJO0J4u5kYndnXcLTv6D7IYLzm+0z9WVA=_16d8127c08d:4d99b:18991ffa","author":"Natasha Murashev","summary":{"direction":"ltr","content":"Lately, I've been thinking a lot about functional programming... in relation to life."},"alternate":[{"href":"https://www.natashatherobot.com/implementation-matters/","type":"text/html"}],"crawled":1569829929101,"title":"Implementation Matters","published":1545498499000,"origin":{"streamId":"feed/http://natashatherobot.com/feed/","htmlUrl":"https://www.natashatherobot.com","title":"Natasha The Robot"},"content":{"direction":"ltr","content":"

Lately, I’ve been thinking a lot about functional programming… in relation to life. While there are lots of complicated and intimidating concepts that are included as part of functional programming, on the base level, functional programming is super simple.

\n

It’s about cleanly transforming a set of inputs into outputs, without the side-effects of changing-state and mutable data. In functional programming, the implementation of the function almost doesn’t matter. The important thing is that it takes in a set of inputs, it transforms them through the implementation, and gives back certain outputs.

\n

But if you really think about the process of transformation, the break down of the original ingredients to create something new, there will be some type of leftover in the transformation process. When you burn wood, you transform it into a fire/warmth, but you also have leftover ash. Your function will take wood as the input and output the warmth, but the implementation will have some leftover ash to it.

\n

Your clean mathematical function will have something left in it. Something very subtle and intangible compared to the output. Some type of energy, an essense perhaps. As the law of physics goes, energy cannot be created or destroyed.

\n

Why is it that there could be two apps on the market that do exactly the same thing (the same output), but one app will have millions of users while the other will have none. I think the answer is in that leftover implementation detail. The successful app was made with love and passion, while the unsuccessful app was made simply to make money. The byproduct of the successful app is love while the byproduct of the unsuccessful app is greed.

\n

The consumer can feel this and is attracted by that subtle love byproduct.

\n

Another real-world example is my grandmother’s banana bread. She makes the best banana bread! Let’s say I ask my grandma for the banana bread recipe and she gives it to me. I will go into my kitchen, follow the recipe exactly, but no matter how much I try, my banana bread will never be the same as my grandmothers. Why? Because my grandmother takes the ingredients and implements their transformation in a way that creates deep love as the byproduct. So while the inputs and outputs of my grandmothers and my banana bread are exactly the same, my implementation creates a different byproduct than my grandmothers.

\n

The implementation really does matter. In fact, I would argue that its subtle byproduct is almost more important than the pure output. It gives essence, a soul, to the output. That essence could be good or bad energy.

\n

With this in mind and the end of the year coming up fast, I’d like to challenge you to think a little differently about New Year’s resolutions. Instead of coming up with new things you’re going to do, consider analyzing the implementation byproducts of the things you’re already doing every day and transforming those if needed.

\n

You might already go to the gym. But are you going to the gym because you hate how your body looks or are you going to the gym because you respect your body as a container for your soul and it’s important for you to keep that container healthy? To the outside world, the output will be the same – you already go to the gym and you will continue going to the gym, but when you change the implementation byproduct from hate for your body (not being pretty or good enough) to love for your body (it’s a vessel for your supreme being), you and everyone around you will feel the difference on that subtle level.

\n

How are you implementing your Github comments at work? Are you communicating with judgment and annoyance (especially at that new intern) or are you commenting with the byproduct of compassion and spirit of teaching?

\n

If you already have a blog, does your implementation stem from a desire for fame and recognition or out of love of sharing what you’re learning?

\n

The byproduct of nature is always love. It keeps creating and nourishing us no matter how bad we keep treating it. We trample the earth with our feet every day, but it still gives us fruit. I wish you all the byproduct of love and compassion in everything you do in the coming year.

\n

Happy Holidays!

\n

Natasha

"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"pnMbeb2l5zEGNlmI1nQ7v6sNtbK5C35P3jb9CziBeoQ=_16d8126dc16:1c07:90d684ff","originId":"https://pewpewthespells.com//blog/website-update-2018.html","fingerprint":"8e21a7fc","title":"Website Stack Update","summary":{"content":"Some info about updates to the website","direction":"ltr"},"alternate":[{"href":"https://pewpewthespells.com//blog/website-update-2018.html","type":"text/html"}],"crawled":1569829870614,"published":1569829870614,"origin":{"streamId":"feed/http://pewpewthespells.com/feed.xml","title":"Samantha Demi's Blog","htmlUrl":"https://pewpewthespells.com/"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","fingerprint":"efab5851","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d996:18991ffa","summary":{"direction":"ltr","content":"

I had the fun of interviewing old friend Daniel Jalkut on the latest episode of The Omni Show.

"},"alternate":[{"href":"https://inessential.com/2019/09/25/i_had_the_fun_of_interviewing_old_friend","type":"text/html"}],"crawled":1569829821629,"published":1569437386000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","fingerprint":"f53acc86","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d995:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0.1 is almost entirely a bug-fix release — see the release notes for the full scoop.

\n

It includes one sort-of new feature: there’s now a checkbox in Preferences for turning off the unread count in the Dock. (It was a hidden pref — now it’s visible.)

\n

Status

\n

Here’s what else we’re working on:

\n
    \n
  • iOS/iPadOS app
  • \n
  • NetNewsWire 5.0.2 for Mac — which will mainly be about performance (yes, we can make it even faster)
  • \n
  • NetNewsWire 5.1 for Mac — tentative feature list includes content extraction and at least one more syncing option (but we might change our minds on these: anything can happen between now and then)
  • \n
\n

We might also distribute NetNewsWire 5.0.2 for Mac on the Mac App Store. No guarantees yet, of course, but work is happening in that direction. This goes to our goal of getting as many people as possible using RSS readers.

"},"alternate":[{"href":"https://inessential.com/2019/09/13/netnewswire_5_0_1_released","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0.1 Released","published":1568408217000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","fingerprint":"5b6c292f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d994:18991ffa","summary":{"direction":"ltr","content":"

Had to get a new key fob at work today — my old one wore out. Just a couple weeks shy of my fifth anniversary at Omni! Time flies.

\n

I figure I’m just over eight years from retiring, so I’m not even halfway done here. :)

"},"alternate":[{"href":"https://inessential.com/2019/09/10/had_to_get_a_new_key_fob_at_work_today_m","type":"text/html"}],"crawled":1569829821629,"published":1568153137000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","fingerprint":"3b5ade1b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d993:18991ffa","summary":{"direction":"ltr","content":"

People have been asking me about supporting iCloud as a sync method for NetNewsWire.

\n

It would be really cool because:

\n
    \n
  • There’s no sign-in
  • \n
  • It’s free — no need to spend money on another service
  • \n
  • It would help broaden the pool of people using RSS, since there would be no additional expense or service they’d need — they could just get going
  • \n
\n

It’s a great idea — no question. Given that my goal is to get as many people as possible using RSS, this makes total sense.

\n

Why we didn’t ship with this feature

\n

For the first release — I still think of it as a 1.0, because it really is — our best bet was to appeal to people already using an existing RSS service. We know that those people like and use RSS, and they’re the people most likely to check out a new RSS app.

\n

(We could have delayed and shipped with support for more existing services, but we figured one was enough to get started with, and we could add other services later. And we are.)

\n

In other words, we tried to make an app that the existing market would like. And that’s the right call when you’re starting out.

\n

Also: iCloud sync makes the most sense when you have both a Mac and an iOS app, and we don’t — the iOS app is still in progress. We totally expect people to use NetNewsWire on the Mac and Unread or Reeder on their iPhone and iPad — and iCloud sync won’t work across apps. This scenario requires using services such as Feedbin.

\n

Why I have no idea when this feature might appear

\n

For any existing RSS service, we can be confident that our effort to support it in NetNewsWire would be successful. This is well-trodden ground: we make some web API calls, integrate with our database, and done. It’s not nothing, but conceptually it’s simple and there’s no cause to worry about technical issues.

\n

But iCloud syncing will mean writing exploratory code and only then finding out if it’s going to work.

\n

Syncing the feeds list should be relatively easy — the real issue is with syncing read/unread/starred states of articles. That means a lot of small records.

\n

Is CloudKit up to this? What are the limits? How fast is it? How reliable?

\n

We just don’t know.

\n

Yes, it’s encouraging that News Explorer has this feature — but that doesn’t tell us much about the limits, reliability, and performance.

\n

Working on this is a risk.

\n

So — as you can imagine — we’re still more keen on supporting existing RSS services, because we know there are plenty of people who for-sure like RSS, and who might like NetNewsWire, but who won’t switch their syncing system just to use NetNewsWire.

\n

That said: I do think we’ll get around to trying this, and I’ll be super-pleased if it works, because it really is a great idea — but we have a bunch of other work to do first. (Including the iOS app!)

"},"alternate":[{"href":"https://inessential.com/2019/09/06/on_syncing_netnewswire_using_icloud","type":"text/html"}],"crawled":1569829821629,"title":"On Syncing NetNewsWire Using iCloud","published":1567817061000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","fingerprint":"ca200f5a","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d992:18991ffa","summary":{"direction":"ltr","content":"

Markos Charatzas writes about his excitement in joining the Apple developer world in 2009 to his eventual disillusionment today.

"},"alternate":[{"href":"https://inessential.com/2019/09/06/_markos_charatzas_writes_https_qnoid_com","type":"text/html"}],"crawled":1569829821629,"published":1567788970000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","fingerprint":"66196df9","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d991:18991ffa","summary":{"direction":"ltr","content":"

A number of people have asked that NetNewsWire show the full web page — right there, in the app — after clicking a link.

\n

The idea is pretty good! It solves two big problems:

\n
    \n
  • You get full content, which is great when a feed contains only summaries or truncated articles
  • \n
  • You don’t have to switch to another app: you can stay right where you are
  • \n
\n

You’d think it’s a no-brainer, and we should just go ahead. But there are other considerations.

\n

One big one is that your ad blockers and privacy extensions won’t run. They work in Safari, but they do not extend to other apps that use WebKit. This means that viewing a web page in NetNewsWire would be less secure and more annoying than viewing the same page in Safari (or whatever your browser is).

\n

This points to one of my design principles: the app should have boundaries. Some features belong in the app, and some features are best left to apps that do that feature way better than NetNewsWire could. One of those things is showing web pages — that’s really a web browser feature.

\n

Having boundaries means we can concentrate on doing a great job at the things that do belong in the app.

\n

(Before you mention SFSafariViewController, recall that it’s iOS-only.)

\n

What about the glory days?

\n

“But Brent! In NetNewsWire 2.0 you added a tabbed browser to NetNewsWire, and it was awesome and a hugely popular feature!”

\n

It was! But times have changed. Many websites are hostile these days. In 2005, this feature was fine — but these days it’s totally not.

\n

A winged messenger arrives with a solution

\n

There is a solution to the problem of showing full content and not leaving the app, and it’s a feature that really does belong in an RSS reader: using content extraction to grab the article from the original page.

\n

If you’ve ever used Safari’s Reader view, then you know what I’m talking about. The idea is that NetNewsWire would do something very much like the Reader view (but inline, in the article pane), that grabs the content and formats it nicely, without all the extra junk that is not the article you want to read.

\n

There are a number of open source options for this. We’re looking at using Feedbin’s content extraction service (which wouldn’t require you to have a Feedbin account).

\n

The generous folks at Feedbin are running a copy of the open-source Mercury Parser, and they’ve offered to open this service up to RSS readers like NetNewsWire. (Reeder uses it already, for instance.)

\n

When?

\n

Right now we’re working on NetNewsWire 5.0.1, which is (almost entirely) a bug-fix release. I don’t know what’s going to be in 5.1 yet — we’re still digesting all the feedback, looking at our original roadmap, and thinking about things.

\n

We’re also working on NetNewsWire for iOS! We’re busy.

\n

But this is definitely the kind of feature that should come sooner rather than later.

"},"alternate":[{"href":"https://inessential.com/2019/09/04/on_the_many_netnewswire_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"On the Many NetNewsWire Feature Requests to Show Full Web Pages","published":1567661107000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","fingerprint":"bb260103","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d990:18991ffa","summary":{"direction":"ltr","content":"

John Gruber has mentioned, on The Talk Show, that I’ve got some weird ideas about what beta means.

\n

Here are my definitions:

\n

development (d): everything is in progress and the app might be completely unusable.

\n

alpha (a): the app is feature-complete and has no known bugs — but, importantly, it’s had very little testing.

\n

beta (b): the app is feature-complete, has no known bugs, and has been tested — but further testing is still warranted. Every beta is a release candidate.

\n

These are defined in a NetNewsWire Technote. It’s important to have definitions that everybody working on or testing the app understands.

\n

But why these rather strict definitions?

\n

It’s part of our commitment to quality. What matters is the end result — the shipping app — and these definitions make sure we don’t get to beta, or even alpha, with the app up on the table with wires sticking out and pieces missing.

\n

This gives us a big space between development and shipping, and that space is all about making sure the bugs are all fixed.

\n

This is a matter of ethics and pride in our work. Absolutely.

\n

But it’s also pragmatic. This is an open source app, written by volunteers in their spare time, and having this rhythm baked-in to the process helps make sure we can uphold our standards even without full-time developers, managers, and testers.

\n

* * *

\n

And… it bugs me how little real attention our industry pays to quality these days. In some cases the consequences are disastrous; in other cases they’re merely expensive. It doesn’t have to be this way.

\n

If it seems like I’m going too far with my definitions, well, I’m trying to bend the stick here.

"},"alternate":[{"href":"https://inessential.com/2019/09/02/on_my_funny_ideas_about_what_beta_means","type":"text/html"}],"crawled":1569829821629,"title":"On My Funny Ideas About What Beta Means","published":1567455823000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","fingerprint":"e526eb19","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98f:18991ffa","summary":{"direction":"ltr","content":"

I love this NetNewsWire write-up on WP Tavern.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/i_love_this_netnewswire_write_up_on_wp_t","type":"text/html"}],"crawled":1569829821629,"published":1567280353000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","fingerprint":"57ea983b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98e:18991ffa","summary":{"direction":"ltr","content":"

NetNewsWire 5.0 is a 1.0 app in disguise.

\n

And so, as expected, we’ve had a ton of feature requests. Most people tend to request one or two features — and there’s a huge variety in these. People want different things.

\n

Nevertheless, there are a few themes we can pick out from what people are asking for:

\n
    \n
  • More syncing options, especially Feedly support
  • \n
  • iOS app
  • \n
  • Some way to deal with partial-content feeds
  • \n
  • Customization of the article pane (fonts, colors, etc.)
  • \n
  • Traditional view (timeline on top with single lines, article below)
  • \n
  • More sharing options (Instapaper, Pinboard, etc.)
  • \n
  • Customizable keyboard shortcuts
  • \n
  • State restoration
  • \n
  • Localizations
  • \n
  • Hiding read items in the timeline (or dimming them)
  • \n
  • Hiding feeds (in the sidebar) that have no unread articles
  • \n
  • User-created smart feeds
  • \n
\n

The less-common, more singular requests are for things like specific sorting options — there are lots of different small options that people would like.

\n

People have also asked for things that might surprise you (they surprised me) — for instance, we’ve had a request for monochrome icons for the toolbar. Another request for a Dark Mode that’s different from Apple’s Dark Mode. Etc.

\n

How We Choose What To Do Next

\n

The first principle is that we can’t lose what we love about the app. We do our damnedest to ship with no bugs, and the app needs to be fast and, most importantly, it needs to feel lighter-than-air.

\n

Whenever you add things — even if the app remains just as fast, even if there are no bugs — you still run the risk of losing that feeling of lightness. One of the quickest ways to lose that feeling is to add a whole bunch of preferences, View menu options, toolbar commands, and other chrome. So we’re going to be very slow to add things like that.

\n

NetNewsWire needs to not become fiddly. (Earlier versions of NetNewsWire got way too fiddly.)

\n

There are other questions we ask about a feature before we do it.

\n
    \n
  • Will it substantially benefit current users?
  • \n
  • Will it bring a number of new users to the app?
  • \n
  • Does the feature depend on something else being done first?
  • \n
  • How much work will it take?
  • \n
  • Does it require resources (such as new icons) that our programmers can’t provide?
  • \n
  • Does the feature really belong in an RSS reader at all?
  • \n
\n

And, because this is an open source app, there’s another dimension: people. Is someone available? Has someone just shown up who’s eager to work on a specific feature? Those things have an impact on scheduling, too.

\n

The good news is that most of the common feature requests are obvious things to do.

\n

Some examples — not nearly everything, just a few thoughts:

\n

The iOS app is in progress. Maurice Parker has been writing it, and it’s coming along very well. Still plenty more to do, and we won’t ship before iOS 13 ships, but it’s happening.

\n

Adding syncing options is a definite good thing for the app. Doing the first one (Feedbin) was the big effort, because it required building the infrastructure that makes syncing possible. Once that was done, adding additional services is not super-difficult. (Not easy, no. Nothing’s trivial. But at least the infrastructure and patterns are in place.)

\n

We’d like to support all the various services, or at least a majority of them. And we have people working on adding services.

\n

Customization of the article pane will most likely work the way it did in older versions of NetNewsWire: we had theme files which included templates and CSS. The app shipped with a few, and you could make your own and use themes other people made.

\n

This feature shipped with NetNewsWire 2.0, and people really loved it. It was fun!

\n

More sharing options is an obvious good idea. Of course you should be able to send to Instapaper, Pocket, Pinboard, and so on. We shipped with custom support for MarsEdit and the Micro.blog app — mainly because I use those apps. But an RSS reader ought to support as many sharing workflows as possible. That’s one of the core points of the app.

\n

* * *

\n

Anyway — the above doesn’t cover everything. Don’t take any of the above as gospel about what we’re doing or when, or what we’re not doing. We haven’t planned 5.1 yet! It’s too soon.

\n

There are also features that we want to do that people haven’t asked for, but that we think are cool. \uD83C\uDFB8

\n

The take-away from this article should be: we’re being very careful about designing and implementing new features, because we have to make sure NetNewsWire doesn’t lose what makes it special.

\n

But we are doing new features, because there are so many things that can make the app even better — we can make it better for current users and we can bring in new users.

"},"alternate":[{"href":"https://inessential.com/2019/08/31/netnewswire_5_feature_requests","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5 Feature Requests","published":1567278518000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/29/follow_through","fingerprint":"444937b","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98d:18991ffa","summary":{"direction":"ltr","content":"

Decades ago, when I was working for Dave Winer at UserLand, I learned about the concept of follow-through after a major release.

\n

If you’re an app maker, it might seem like your goal is to get to release day. Get the app done, make it available, publish an announcement, and then get back to coding. Let the world do what it’s going to do.

\n

One bang, and then back to work, in other words.

\n

But that’s not going to maximize your chances for a good release. You need to follow through — you need to keep going.

\n

Some of the things you might do, in no particular order:

\n
    \n
  • Publish tips on using your app — one a day or so
  • \n
  • Update your website with feedback, testimonials, and good reviews
  • \n
  • Be available and communicative about your app
  • \n
  • Go on some podcasts
  • \n
  • Write about how release day went
  • \n
  • Write about plans for the x.0.1 version
  • \n
  • Field bug reports and feature requests gratefully
  • \n
  • Thank reviewers who’ve done a good job
  • \n
  • Make it as easy as possible for reporters and reviewers to get access to your app and to you
  • \n
  • Work to build a community of customers, on Slack or similar
  • \n
\n

I’m sure you can think of more things to do — the above isn’t everything, and every app is different.

\n

But the key is that you don’t just do the release and then stop. Instead, show that you‘re responsive, show that your app has momentum, show that you care enough to keep showing up.

\n

For me, at least, this is the fun part. I realize that’s not true for everybody — but you should do it anyway. \uD83C\uDFA9

"},"alternate":[{"href":"https://inessential.com/2019/08/29/follow_through","type":"text/html"}],"crawled":1569829821629,"title":"Follow-Through","published":1567110304000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","fingerprint":"669e65c4","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98c:18991ffa","summary":{"direction":"ltr","content":"

We have a few reports of a crash where the add-feed-sheet window doesn’t load. There’s a line of code with window! — because of course we expect the window to have been loaded — and it crashes right there.

\n

This crash made zero sense to me, but Daniel Jalkut figured out the most likely cause and was able to reproduce it: it’s because the person has moved the app (from one folder to another) after launching it, while it’s running, and the nib-loading machinery can’t find the nib, because it’s moved along with the app.

\n

Tip: if you’re going to move an app, quit it first, then move it, and then re-launch it!

\n

At any rate: our fix for this will be to load that sheet on startup, and then recycle it on each use. This fix will go into NetNewsWire 5.0.1.

\n

This just fixes the bug with this one nib, though. A more systematic fix — maybe just a warning to the user suggesting they quit and re-launch — would be a good idea.

\n

File under “bugs iOS developers never have to worry about.” \uD83D\uDC07

\n

PS We have a 5.0.1 beta milestone now.

"},"alternate":[{"href":"https://inessential.com/2019/08/28/daniel_figures_out_one_of_the_two_crashi","type":"text/html"}],"crawled":1569829821629,"title":"Daniel Figures Out One of the Two Crashing Bugs","published":1567022746000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/27/how_release_day_went","fingerprint":"2394a816","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98b:18991ffa","summary":{"direction":"ltr","content":"

Yesterday was a great day! A few things to note, in no particular order:

\n

NetNewsWire got some press coverage, including a well-done review in MacStories.

\n

We got a lot of feature requests, but no bug reports.

\n

Except that we did get a single-digit number of crash logs. On investigation, I found two distinct backtraces — we’ll need to fix those. The thing is, there’s no freakin’ way the app should crash in those spots. Except that, obviously, it can. Rarely, but it happens.

\n

The servers started timing-out at one point during the day. I contacted DreamHost support and they fixed things (and told me that the fixes they applied should prevent this in the future).

\n

There were a number of nice blog posts and tweets about NetNewsWire, which was awesome. After working so hard for so long, it’s great when people appreciate the app. We don’t get paid in money, after all. \uD83D\uDC23

\n

I have no idea how many downloads of the app there were. GitHub is hosting the download, via its releases feature, and I don’t see a way to find out how many times it’s been downloaded. Which is totally fine with me.

\n

* * *

\n

I should say something more about the no-bug-reports. There’s no special magic or talent or anything to this — there’s just the willingness to say that we’re not going to ship until we’ve got the bugs out, and then sticking to that.

\n

This is a matter of pride and ethics, for sure, but there’s another dimension: since the app is open source, it’s written by volunteers (including me), and we have no dedicated support team. Any time we spend fielding bug reports is time taken away from working on the next feature.

\n

Making apps — even, or especially, free apps — is an exercise in economics. With free apps, the economics are even more constrained, because nobody is going to hire even a part-time support person. So we do everything we can do keep costs down — especially time costs.

\n

Plus — buggy apps can be demoralizing to the people who work on them. Part of my job is to make sure people are proud and happy to work on the app. And that means making sure everyone knows we’re super-serious about doing our best to never ship bugs.

"},"alternate":[{"href":"https://inessential.com/2019/08/27/how_release_day_went","type":"text/html"}],"crawled":1569829821629,"title":"How Release Day Went","published":1566937707000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","fingerprint":"175d2cdb","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d98a:18991ffa","summary":{"direction":"ltr","content":"

\"NetNewsWire

\n

NetNewsWire 5.0 is shipping!

\n

In case you haven’t been following along until just now: NetNewsWire is an open source RSS reader for Mac. It’s free! You can just download it and use it. No strings.

\n

It’s designed to be stable, fast, and free of bugs. It doesn’t have a lot of features yet, and that’s because we prioritized quality over features. We will be adding more features, of course, but not quickly. We’re also working on an iOS app.

\n

It syncs using Feedbin. We’ll support more systems in the future (as many as possible).

\n

I hope you like it!

\n

Some links…

\n\n

Thanks to so many people

\n

I want to especially thank Sheila Simmons and my family and friends.

\n

This release took five years to make, and for four of those years it wasn’t even called NetNewsWire. It was just a year ago that I got the name NetNewsWire back from Black Pixel — and I thank them again for their wonderful generosity.

\n

I also want to thank Brad Ellis for making the beautiful app icon and toolbar icons. Thanks to our major code contributors: Maurice Parker, Olof Hellman, and Daniel Jalkut. Thanks to Ryan Dotson for writing the Help book. Thanks to Joe Heck for looking after infrastructure issues (especially continuous integration).

\n

Thanks to my co-workers and friends at The Omni Group (which is a wonderful place to work). Thanks to the ever-patient and ever-awesome NetNewsWire beta testers on the Slack group and elsewhere.

\n

And thanks to everyone who’s ever used the app in its 17-years-and-counting run. Because of you, NetNewsWire has been, and remains, the thrill of my career.

"},"alternate":[{"href":"https://inessential.com/2019/08/26/netnewswire_5_0_now_available","type":"text/html"}],"crawled":1569829821629,"title":"NetNewsWire 5.0 Now Available","published":1566834451000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","fingerprint":"e30daaa8","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d989:18991ffa","summary":{"direction":"ltr","content":"

This is a little bit of bad news. It’s not my intention, and it’s not what I want to happen — but NetNewsWire 3.3.2 apparently does not launch in the next version of macOS (10.15, Catalina).

\n

It links to the PubSub framework, which is not included with the next macOS.

\n

NetNewsWire 3.3.2 was the last release of the full version that I worked on, before selling NetNewsWire to Black Pixel, and I’ve heard from lots of people that they’ve been using it ever since. They never switched.

\n

I would rather it continued working forever, but that’s not to be. Not my choice. Sorry about that!

"},"alternate":[{"href":"https://inessential.com/2019/08/22/end_of_the_line_for_netnewswire_3_3_2","type":"text/html"}],"crawled":1569829821629,"title":"End of the Line for NetNewsWire 3.3.2","published":1566515704000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","fingerprint":"c583e740","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d988:18991ffa","summary":{"direction":"ltr","content":"

The NetNewsWire blog has the details on NetNewsWire 5.0b5 — which should be the last beta.

\n

Still planning to do the 5.0 final release Monday morning, which really means doing the release on Sunday and pushing an announcement to this blog Monday morning. :)

\n

The last things on my to-do list are actually writing that announcement and doing screenshots for the NetNewsWire web page. Easy. \uD83D\uDC2F

"},"alternate":[{"href":"https://inessential.com/2019/08/21/the_netnewswire_blog_has_the_details_on_","type":"text/html"}],"crawled":1569829821629,"published":1566452581000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/20/immunization","fingerprint":"39a4bdb0","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d987:18991ffa","summary":{"direction":"ltr","content":"

Before every major release I like to try and think of everything mean that people might say about the app. It’s fun!

\n

So we just went through this exercise on the NetNewsWire Slack group. Here’s a taste:

\n
    \n
  • This took five years? I could write an RSS parser in a weekend.
  • \n
  • Can’t get my Twitter and Facebook feeds. Whatever.
  • \n
  • Doesn’t work with my Usenet host.
  • \n
  • The information density of the timeline is… lacking. What the hell.
  • \n
  • Not truly open source since it’s on a Mac.
  • \n
  • Not truly open source since it’s not GPL.
  • \n
  • No vim keys. Why bother.
  • \n
  • Regular people will never use an RSS reader. What’s the point?
  • \n
  • Brent’s last good idea was in 2002. Consider this a textbook case of coasting.
  • \n
  • Great app. Too bad RSS died with Google Reader.
  • \n
  • It totally didn’t pick up my subscriptions from the earlier version. How is this an upgrade?
  • \n
  • When does a 5.0 have fewer features than a 3.0? When it’s NetNewsWire.
  • \n
  • The echo chamber will love this app. They always do.
  • \n
  • Free app. Continues the race to the bottom. Pour one out for Silvio Rizzi.
  • \n
  • No way to send to Instapaper. Fuck it.
  • \n
  • Brent Simmons can’t stop pursuing a technology that even Google famously admitted was not worth bothering with.
  • \n
  • If this app took five years, imagine how long it will take before it will actually sync with Feedly.
  • \n
  • Sure it’s free, but I bet the Feedbin people paid them off, because the only way to sync is to pay money to Feedbin.
  • \n
  • No iCloud sync? Jerks.
  • \n
  • No iOS app. The revolution happened on mobile, Brant. What the actual fuck.
  • \n
  • Shoulda been Catalyst. Dinosaurs wrote this app.
  • \n
  • Not on the Mac App Store? I guess they don’t want users.
  • \n
  • I would totally use this if it had just this one [feature x], which I can’t believe they shipped without. (Multiply this comment by 100, with a different feature x each time.)
  • \n
  • Area Man Can’t Let RSS Go
  • \n
\n

Some feedback will be factually inaccurate, but we like to imagine that too:

\n
    \n
  • I remember using NetNewsWire on OS 9, and it hasn’t really improved since then. They should make it a Cocoa app.
  • \n
  • Doesn’t work with web comics. POS
  • \n
  • Doesn’t support 10.5.
  • \n
  • It should be free.
  • \n
  • You’d think they would have updated the design — but it looks exactly like NetNewsWire of old.
  • \n
  • Why the hell would they build on that aging code base from Black Pixel? I heard it doesn’t even use ARC.
  • \n
  • No way to sync? What’s their actual problem?
  • \n
\n

See? The actual feedback will be nicer than the stuff we thought up. This provides a bit of immunization. :)

\n

But, also, there will be negative feedback we didn’t imagine. That’s the gold!

\n

* * *

\n

Bonus from Daniel Jalkut, but not actually a criticism:

\n
\n

Can’t innovate, my RSS.

\n
"},"alternate":[{"href":"https://inessential.com/2019/08/20/immunization","type":"text/html"}],"crawled":1569829821629,"title":"Immunization","published":1566332363000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"originId":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","fingerprint":"592043f","id":"ITR2bp1hhxjNSFKlSuZR7gUTTcxmHRq2TwhCgV9CifI=_16d81261cbd:4d986:18991ffa","summary":{"direction":"ltr","content":"

I think we’re still on track for releasing NetNewsWire 5.0 Monday, August 26. There will be one more beta before then.

\n

I’ll be available for podcasts, interviews-via-email, etc. If you’d like to set something up, email me or DM me on Twitter.

"},"alternate":[{"href":"https://inessential.com/2019/08/19/i_think_were_still_on_track_for_releasin","type":"text/html"}],"crawled":1569829821629,"published":1566259329000,"origin":{"streamId":"feed/http://inessential.com/xml/rss.xml","htmlUrl":"https://inessential.com/","title":"inessential.com"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]},{"id":"zl8fQ5xTLhjt3vC6/I1d1cHoXwVcArEYV8/6kICnZkA=_16d8125cccf:1a5d:90d684ff","originId":"http://www.mechanicalgirl.com/post/using-google-cloud-functions-create-simple-post-endpoint-handle-data/","fingerprint":"6c878b73","title":"Using Google Cloud Functions to Create a Simple POST Endpoint","summary":{"content":"

\nI was tempted to title this "how to use GCF to create a simple ETL process", but that's not quite what I'm demonstrating here.\n

\n

\nIt does loosely fit the description of an ETL process - the script extracts some values from a POSTed payload, rearranges some of the values to fit a specific schema, then loads the transformed payload into a data store.

\n

\nBut what you're going to see here is not the heavy lifting we normally think of when we see the acronym "ETL".\n

\n

\nAnd maybe that's a good thing, as it illustrates the beautiful simplicity of Google's new Cloud Function service.\n

\n

Some background:

\n

\nI work on a data infrastructure team that already has an account and a project set up on Google Cloud Platform. That project is already associated with a data store - a BigQuery project/dataset. I'm not going to cover how to set all that up since it's out of scope here, but you can start with these docs: https://cloud.google.com/docs/\n

\n

\nI'm currently working on a project to accept realtime event data from a media platform we work with. We expect the data to come in at a medium-to-high volume, but we're still in testing so I don't have details on how well this job will handle the volume or how well it will scale - that will come later.\n

\n

The project:

\n

\nWhat I am going to talk about is this flow, with some general info on how to build the tools I needed to handle each step:\n

\n
    \n
  • vendor POSTs the event data payload to my http endpoint
  • \n
  • we receive, validate, and transform the payload data
  • \n
  • we write that data to a BigQuery table
  • \n
\n

\nThe pieces I had to build to do this:\n

\n
    \n
  • a Google Cloud Function
  • \n
  • a BigQuery table
  • \n
\n

gcloud:

\n

\nBefore we go much further - assuming that you already have a Google Cloud project, with a BigQuery dataset, and all the permissions set up to link the two - you will also need the gcloudcommand line tool. Go here and follow the steps to install:\n

\n

\nhttps://cloud.google.com/sdk/docs/quickstart-macos\n

\n

\ngcloud is what you'll use to deploy your function to Google Cloud. Installation will update your PATH to include the Google Cloud SDK in ~/.bash_profile. You may need to go through some authorization steps using the email address you have associated with your project. You may not need to add any gcloud components, although if you do instructions are included in the installation output.\n

\n

\nFor the example here, you should probably have these components:\n

\n
    \n
  • BigQuery Command Line Tool
  • \n
  • Cloud SDK Core Libraries
  • \n
  • Cloud Storage Command Line Tool
  • \n
\n

Setting up the script:

\n

\nIn a local folder, do some of the basic setup you normally would to start a Python project:\n

\n
    \n
  • create a main.py - this wil be your script
  • \n
  • add a requirements.txt for any libraries you might need to install
  • \n
  • use virtualenv to keep everything contained, particularly if you're going to test locally
  • \n
\n

\nIn your main.py, you are free to build your Python script in whatever way works for you. You can import any libraries you might need, you script structure can be as simple or as complex as you need it to be.\n

\n

\nThe only key requirement is that you name a function that will be the entry point for your script - that name will be how your function is referenced in the GCP dashboard, and will be used to deploy the code to GCP.\n

\n

The code:

\n

\nNow (finally!) let's look at some sample code:\n

\n

\nIn main.py, I've built a simple Flask app (I love how easily Flask handles POSTs).\n

\n
\nimport json\n\nfrom flask import Flask, request\nfrom google.cloud import bigquery\n\napp = Flask(__name__)\n

\n

\nI'll use this schema both to create my BigQuery table and to insert rows. This schema example includes several common column types used in BigQuery.\n

\n
\nschema = [\n    bigquery.SchemaField('timestamp', 'TIMESTAMP', 'NULLABLE'),\n    bigquery.SchemaField('event_type', 'STRING', 'NULLABLE'),\n    bigquery.SchemaField('event_id', 'STRING', 'NULLABLE'),\n    bigquery.SchemaField('has_insights', 'BOOLEAN', 'NULLABLE'),\n    bigquery.SchemaField('video_insights', 'RECORD', 'REPEATED',\n        fields=[\n            bigquery.SchemaField('video_id', "STRING", 'NULLABLE'),\n            bigquery.SchemaField('video_duration', "INTEGER", 'NULLABLE'),\n        ],\n    ),\n    bigquery.SchemaField('categories', 'STRING', 'REPEATED'),\n]\ndataset = 'my_dataset'\ntable_name = 'my_events_table'\n

\n

\nHere's the events() function that does a few things:\n

\n
    \n
  • simple validation of the POST contents, the 'payload' (make sure it's JSON and that 'events' are included)
  • \n
\n
\ndef events(request):\n    payload = {}\n    try:\n        payload = request.get_json()\n        events = payload['events']\n    except Exception as e:\n        response = app.response_class(\n            response=json.dumps({'error': e.message}),\n            status=400,\n            mimetype='application/json'\n        )\n        return response\n

\n
    \n
  • create the table in BigQuery (or not - this can be done in a few different ways, I've just included the example here as a convenience)
  • \n
\n
\n    try:\n        create_table()\n    except Exception as e:\n        print("ERROR", e)\n

\n
    \n
  • for each event, extract a few values, add them to a dict, add that dict to a new list (this is the 'transform' part of our mini-ETL)
  • \n
\n
\n    event_rows = []\n    for p in payload['events']:\n        entry = construct_entry(payload, p)\n        event_rows.append(entry)\n

\n
    \n
  • take that list of transformed events and insert them into the BigQuery table all at once
  • \n
\n
\n    try:\n        insert_entries(event_rows)\n    except Exception as e:\n        print("Error on inserting entries: %s" % e)\n        sys.exit()\n

\n
    \n
  • and if it's all successful, return a 200
  • \n
\n
\n    response = app.response_class(response='', status=200)\n    return response\n

\n

\nHere are the utility functions that use the google.cloud.bigquery library to do all that stuff:\n

\n
    \n
  • create a BigQuery table
  • \n
  • transform each event into an entry
  • \n
  • load the list of entries into the BigQuery table
  • \n
\n
\ndef create_table():\n    client = bigquery.Client()\n    dataset_ref = client.dataset(dataset)\n    table_ref = dataset_ref.table(table_name)\n    table = bigquery.Table(table_ref, schema=schema)\n    table = client.create_table(table)\n    print("Created table {}".format(table.full_table_id))\n    return table.full_table_id\n\ndef construct_entry(payload, event):\n    insights_list = []\n    if event.get('video_insights', None):\n        for i in event['video_insights']:\n            v = {\n                'id': vp.get('video_id', ''),\n                'time_played': vp.get('video_duration', 0),\n            }\n            insights_list.append(v)\n    entry = {\n        'timestamp': event.get('timestamp', None),\n        'type': event.get('event_type', ''),\n        'id': event.get('event_id', ''),\n        'categories': event.get('categories', []),\n        'has_insights': event.get('has_insights', False),\n        'insights': insights_list,\n    }\n    return entry\n\ndef insert_entries(event_rows):\n    client = bigquery.Client()\n    dataset_ref = client.dataset(dataset)\n    table_ref = dataset_ref.table(table_name)\n    table = bigquery.Table(table_ref, schema=schema)\n\n    try:\n        response = client.insert_rows(table, event_rows)\n    except Exception as e:\n        print("Error: %s" % str(e))\n        return False\n    return True\n

\n

\ngoogle.cloud.bigquery docs are here: https://googleapis.github.io/google-cloud-python/latest/bigquery/reference.html\n

\n

\nFinally here's the main method that uses Flask to run the app and route requests to the events() function:\n

\n
\nif __name__ == '__main__':\n    app = Flask(__name__)\n    app.route('/events', methods=['POST'])(lambda: events(request))\n    app.run(debug=True)\n

\n

\nAltogether, that's only 100 lines of code! My example does work with a simplified payload, so as always, your mileage may vary.\n

\n

\nI only have three libraries in my requirements.txt:\n

\n
\nFlask==1.0.2\ngoogle-cloud-bigquery==1.3.0\ngoogle-cloud-storage==1.6.0\n

\n

The sample payload:

\n

\nAnd here's what you would expect a valid payload to look like for this example:\n

\n
\n{\n  "site_id": "example.com",\n  "another_id": "FE7169C2",\n  "events": [\n    {\n      "timestamp": "2017-07-25T09:15:36Z",\n      "event_type": "ARTICLE_VIEW_EVENT",\n      "event_id": "28C86A6C-B93F-4445-94D0-5926F6C0F723",\n      "categories": ['Technology', 'Computers', 'News'],\n      "has_insights": false\n    }, {\n      "timestamp": "2017-07-25T10:03:12Z",\n      "event_type": "ARTICLE_VIEW_EVENT",\n      "event_id": "A7ED75A5-475E-44EE-BAD9-3A57D8F547B2",\n      "categories": ['Entertainment', 'Games'],\n      "has_insights": true\n      "video_insights": [\n        {\n          "video_id": "video1",\n          "video_duration": 120\n        }\n      ]\n    }\n  ]\n}\n

\n

Deploying:

\n

\nTo deploy from the command line, make sure you're in the folder with the function code. Then run:\n

\n
\ngcloud beta functions deploy events --trigger-http --runtime python37 --project my-project-name\n

\n

\nBreaking it down:\n

\n
    \n
  • gcloud beta functions deploy is the command that lets you create or update a Google Cloud Function
  • \n
  • events is the name of the Google Cloud Function (as defined in example source code) that will be executed - you'll also see this in the dashboard
  • \n
  • we specify --trigger-http to generate an http endpoint where our function can receive requests
  • \n
  • runtime is the execution environment (there are also node.js and golang options)
  • \n
  • finally, you need to include your project name
  • \n
\n

\nFor more info about constructing a deployment, run:\n

\n
\ngcloud beta functions deploy --help\n

\n

\nWhen your deploy is successful, you'll see the entry point/http trigger values included in the return message, looking something like this:\n

\n
\nentryPoint: events\nhttpsTrigger:\n  url: https://region-my-project-name.cloudfunctions.net/events\n

\n

\nAnd that's it! If you use CURL to post a valid payload to your new endpoint, you should shortly thereafter see a few records in your BigQuery table.\n

\n

Etcetera:

\n

\nI would guess that HTTP endpoints are going to be the most common use for Google Cloud Functions, but there are several other trigger types available. For more, take a look at:\n

\n

\nhttps://cloud.google.com/functions/docs/calling/\n

\n

\nYou should put your code in Github or whatever your choice of repository is, but be aware that GCP also stores the most recent version of the source code. In your project, navigate to the functions dashboard, e.g.:\n

\n
\nhttps://console.cloud.google.com/functions/list?project=my-project-name\n
\n

\nAnd click through to your-function-name >> Source.\n

\n

\nIf you poke around your function dashboard on the GCP console, you'll also find some fun stuff like usage and activity charts, logging, and a little inline testing module.\n

\n

\nFinally, here's a really good primer on GCF. This post is what that got me started:\n

\n

\nServerless Python Quickstart with Google Cloud Functions (Dustin Ingram)\n

","direction":"ltr"},"alternate":[{"href":"http://www.mechanicalgirl.com/post/using-google-cloud-functions-create-simple-post-endpoint-handle-data/","type":"text/html"}],"crawled":1569829801167,"published":1569829801167,"origin":{"streamId":"feed/http://www.mechanicalgirl.com/feeds/all/","title":"MechanicalGirl","htmlUrl":"http://www.MechanicalGirl.com/"},"visual":{"url":"http://www.fubiz.net/wp-content/uploads/2013/10/Modern-Eco-Friendly-Home5-493x1024.jpg","width":493,"height":1024,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8","label":"Uncategorized"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/Feedly/Initial/weblogs_initial.json b/Frameworks/Account/AccountTests/Feedly/Initial/weblogs_initial.json new file mode 100644 index 000000000..df70e510d --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/Initial/weblogs_initial.json @@ -0,0 +1 @@ +{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","updated":1569830765949,"continuation":"16d74074eb8:2058:90d684ff","items":[{"originId":"https://useyourloaf.com/blog/scroll-view-layouts-with-interface-builder/","fingerprint":"3edfc038","id":"AVw5dsyytPMMU/iCsYRiYJstfLF5CXoDiC5tFHJ2Gfk=_16d8134857d:4da39:18991ffa","author":"keith@useyourloaf.com (Keith Harrison)","summary":{"direction":"ltr","content":"

Laying out a scroll view is a confusing task. I found it easier when, in iOS 11, Apple introduced frame and content layout guides. Too bad they neglected to add them to Interface Builder. That changed in Xcode 11. Here’s a quick guide on how to use them.

\n

Why Are Scroll View Layouts So Hard?

\n

For a recap on why scroll views are confusing and how the frame and content layout guides help see this earlier post on easier scrolling with layout guides. Here’s the layout I built in code using layout guides:

\n

\"Scrolling

\n

You need two groups of constraints to layout a scroll view:

\n
    \n
  • constraints that fix the frame (size and position) of the scroll view relative to its superview.
  • \n
  • constraints that layout the content relative to the content area of the scroll view and constrain its size.
  • \n
\n

Building my layout with Interface Builder and Xcode 10 I might end up with constraints like this:

\n

\"Constraining

\n
    \n
  1. Four constraints pin the scroll view to the edges of the root view, fixing its frame.
  2. \n
  3. Four constraints between the stack view and the scroll view pin the content to the scroll view content area.
  4. \n
  5. The width of the stack view is fixed to the width of the scroll view frame.
  6. \n
\n

I never found this to be very intuitive:

\n
    \n
  • Constraints between the scroll view and a super view act on the frame of the scroll view.
  • \n
  • Constraints between the scroll view and a subview act on the content area of the scroll view.
  • \n
  • Height or width constraints between the scroll view and a subview use the frame of the scroll view.
  • \n
\n

The introduction of the frame and content layout guides in iOS 11 promised to make things clearer. Unfortunately, Apple neglected to add support for them in Interface Builder.

\n

What Changed In Xcode 11

\n

From the Xcode 11 release notes:

\n
\n

Content and Frame Layout guides are supported for UIScrollView and can be enabled in the Size inspector for more control over your scrollable content. (29711618)

\n
\n

The content and frame layout guides are enabled by default in Xcode 11 when you drag a new scroll view into the canvas:

\n

\"Scroll

\n

You can enable them for an older layout using the size inspector:

\n

\"Enable

\n

The best way to explain how to use them is with an example.

\n

Using The New Layout Guides In Interface Builder

\n

Let’s build the previous layout in Interface Builder using the content layout guides:

\n
    \n
  1. To get started I have arranged the content for my layout in a vertical stack view that I have embedded in a scroll view in Interface Builder:

    \n

    \"Embedded

  2. \n
  3. Let’s fix the size and position of the scroll view by pinning it to the edges of the root view. I find that easier to do if you first hide the safe area layout guide for the root view (using the size inspector):

    \n

    \"Hide

    \n

    Select the scroll view and then use the “Add New Constraints” tool to add the four constraints between the scroll view and the edges of the root view:

    \n

    \"Pin

    \n

    Check the constraints in the navigator to make sure you’re not using the safe area or accidentally adding padding to any of the constraints:

    \n

    \"Scroll

  4. \n
  5. We want to pin the stack view to the edges of the content area. Control-drag from the stack view to the Content Layout Guide:

    \n

    \"Using

    \n

    Then add the four constraints to pin each edge of the stack view to the guide:

    \n

    \"Constraints

    \n

    Check the constraints in the navigator:

    \n

    \"Content

  6. \n
  7. I don’t want the layout to scroll horizontally so I also need to fix the width of the content area to match the width of the scroll view frame. It’s not possible to create an equal width constraint between the content and frame layout guides in Interface Builder. Instead, control-drag from the stack view to the frame layout guide:

    \n

    \"Using

    \n

    Then add an equal widths constraint:

    \n

    \"Equal

    \n

    The final set of nine constraints using both the content and frame layout guides:

    \n

    \"Completed

  8. \n
\n

What’s Still Missing?

\n

I don’t want to complain. It’s good to see the content layout guides show up in Interface Builder. It’s been two years since they were introduced with iOS 11 but better late than never. To complete the picture I’d like to see Apple also expose the layout margin guide for the scroll view.

\n

Sometimes you want a view that remains fixed in the scroll view frame so that it floats over the scrollable content. For example, the info button in my previous example remains fixed in the top-left corner when the content scrolls:

\n

\"Info

\n

The frame layout guide doesn’t help in this case as it extends into the safe area behind the navigation bar. I created constraints in code to fix the button to the layout margins guide of the scroll view:

\n
infoButton.leadingAnchor.constraint(equalTo:\n           scrollView.layoutMarginsGuide.leadingAnchor),\ninfoButton.topAnchor.constraint(equalTo:\n           scrollView.layoutMarginsGuide.topAnchor)\n
\n

If you try to do that in Interface Builder the constraints are assumed to be with the content area so the button scrolls with the content. The closest you can get is to use the safe area layout guide of the scroll view (enable it in the size inspector) with some extra padding:

\n

\"Scroll

\n

See The Code

\n

You can find the code for this post in my Code Examples GitHub repository. You can compare it to the original ScrollGuide project which builds the layout in code.

\n

Read More

\n
\n

\nScroll View Layouts With Interface Builder was originally posted 16 Sep 2019 on useyourloaf.com.\n

\n

Want this direct to your inbox? Sign up and get my free iOS Size Classes guide.\n

\n
"},"alternate":[{"href":"https://useyourloaf.com/blog/scroll-view-layouts-with-interface-builder/","type":"text/html"}],"crawled":1569830765949,"title":"Scroll View Layouts With Interface Builder","published":1568624542000,"origin":{"streamId":"feed/http://useyourloaf.com/blog/rss.xml","htmlUrl":"https://useyourloaf.com/blog/","title":"Use Your Loaf - iOS Development News & Tips"},"visual":{"url":"http://b.vimeocdn.com/ts/442/609/442609877_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"originId":"https://useyourloaf.com/blog/editing-a-swift-package/","fingerprint":"1299da46","id":"AVw5dsyytPMMU/iCsYRiYJstfLF5CXoDiC5tFHJ2Gfk=_16d8134857d:4da38:18991ffa","author":"keith@useyourloaf.com (Keith Harrison)","summary":{"direction":"ltr","content":"

When I want to make changes to a Swift package, I find it useful to do it in the context of a project that depends on that package. Unfortunately, you can’t directly edit a Swift package you add as a dependency to an Xcode project. It’s read-only and managed by Xcode. What you can do is add a local copy of the package that overrides the package dependency.

\n

Swift Package Dependencies

\n

I previously covered creating Swift packages in Xcode 11 and then adding them as a dependency to a project. Xcode downloads the package files from the remote repository:

\n

\"Xcode

\n

You can browse the source files in the package, but you cannot edit them in Xcode (which is probably for the best).

\n

Editing A Standalone Package

\n

You always have the option to clone the package repository to a new folder:

\n
$ git clone <URL of Git repository>\n
\n

\"Finder

\n

You can then open the Package.swift file in Xcode and work on the package directly:

\n

\"Package

\n

After you’ve made your changes, commit the package back to the remote repository.

\n

This is cumbersome if you want to try your updated package with an existing Xcode project. To get Xcode to download the updates from the remote repository you either need to tag the commit with a new version or change the project dependencies to refer to the specific commit:

\n

\"Package

\n

Luckily, there’s a better way.

\n

Editing A Local Copy

\n
    \n
  1. Clone the package repository to a local directory as before.

  2. \n
  3. Drag the whole folder into the Xcode project that has a dependency on the package:

  4. \n
  5. Xcode overrides the package dependency with the same name as the local package. Notice in the screenshot how the Swift Package dependency disappears from the file navigator.

    \n

    \"File

  6. \n
  7. Make your changes to the local package and test them with the project before pushing the changes to the remote repository.

  8. \n
  9. Release the changes by tagging the commit and then pushing the tag:

    \n

    \"Push

  10. \n
\n

To switch back to using the remote package:

\n
    \n
  1. Delete the local copy of the package from the project.

  2. \n
  3. Make Xcode update the package dependencies File > Swift Packages > Update To Latest Package Versions.

  4. \n
  5. You should now see the latest version of the remote package in the file navigator:

    \n

    \"Updated

  6. \n

\n

\nEditing A Swift Package was originally posted 19 Aug 2019 on useyourloaf.com.\n

\n

Want this direct to your inbox? Sign up and get my free iOS Size Classes guide.\n

\n
"},"alternate":[{"href":"https://useyourloaf.com/blog/editing-a-swift-package/","type":"text/html"}],"crawled":1569830765949,"title":"Editing A Swift Package","published":1566205065000,"origin":{"streamId":"feed/http://useyourloaf.com/blog/rss.xml","htmlUrl":"https://useyourloaf.com/blog/","title":"Use Your Loaf - iOS Development News & Tips"},"visual":{"url":"http://b.vimeocdn.com/ts/442/609/442609877_1280.jpg","width":1280,"height":720,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"keywords":["Tech Life"],"originId":"https://randsinrepose.com/?p=4326","fingerprint":"84f0fac2","id":"xoPmejgsoSEqDaFR4LV9EjzHAN8HPWA8MceMFxcQR3k=_16d812a78cb:4da16:18991ffa","author":"rands","summary":{"direction":"ltr","content":"(Ok, first hit play on the above video. This article has a soundtrack.) In World of Warcraft, Darkshore is the first major city outside of the Night Elf starting area and stands in dark contrast to the mystical and deeply saturated pink realm of Teldrassil. It’s where you discover that the game is full of… More"},"alternate":[{"href":"https://randsinrepose.com/archives/ok-im-in-darkshore-and-it-just-started-raining/","type":"text/html"}],"crawled":1569830107339,"title":"Ok, I’m in Darkshore, and It Just Started Raining","published":1569074623000,"origin":{"streamId":"feed/http://www.randsinrepose.com/index.xml","htmlUrl":"https://randsinrepose.com","title":"Rands in Repose"},"content":{"direction":"ltr","content":"

\n

(Ok, first hit play on the above video. This article has a soundtrack.)

\n

In World of Warcraft, Darkshore is the first major city outside of the Night Elf starting area and stands in dark contrast to the mystical and deeply saturated pink realm of Teldrassil. It’s where you discover that the game is full of many people of many races. I’ve spent just over twenty hours in the game over a week, I’m crossing level 17, and it just started raining.

\n

As part of their fifteenth anniversary, Blizzard released the original “vanilla” version of World of Warcraft (“WoW”). This release represents the initial version of the wildly popularly MMO that consumed years of my life. There is a service on the internet that will tell me precisely the number of hours, days, weeks, and years I’ve already spent on WoW, but there is no way I’m going to look because if the total time played isn’t years, it’s undoubtedly months and months.

\n

For me, the act of purchasing and installing Vanilla was a multi-week agonizing process. I’d log into Battle.net, move my mouse cursor over the Subscribe Now button and sit there. Am I ready for this? Do you remember how much time you spent raiding Molten Core? How about Zul’Gurab? Years. Do you need more missing months?

\n

But I had to know. Having played most of the subsequent releases of WoW and watched how the game evolved, I wanted to see if the original spark, the unique hook, was still there. Sure, I was nostalgic, but was the grind going to be worth it?

\n

It is.

\n

A Punishing Grind

\n

Everything and I mean everything is a grind in Vanilla WoW whether it’s increasing your base level, your professions, and/or your secondary skills. Nothing comes from free. You must devote significant time to any attribute you want to grow, and that means finding resources and converting them into useful goods that you either use or sell.

\n

I choose leather-working as one of my professions which pairs nicely with my other profession: skinning. Efficient, right? Skin the animals that I kill and turn those skins into useful materials. Sure, except this is Vanilla where drop rates for many items are anemic. Need five bear skins? Ok, go kill 50 or so bears. Where are these bears? Well, there’s a bunch in the north part of Darkshore which is a five-minute walk or I can head south where there are more bear clusters, but that’s more like a ten-minute walk. No fast travel, no mounts, I need to point my Night Elf south and walk down a forest path for ten minutes… in the rain.

\n

Yes, there are other quests to do on my long walk to the bear massacre, but these quests contain the same DNA as the rest of Vanilla. Nothing for free plus an enormous grind. Lengthy collection quests with frustratingly low drop rates. Oh yeah, did I mention that if I leave a level-appropriate area, I’m going to die? The moment I run into an over-leveled baddie, I’m dead. When this happens (and it will), I appear as a ghost at a usually not nearby cemetery where I am required to run back to my corpse where I will likely to die again at the hands (claws, paws, whatever…) of the same vastly overpowered baddie.

\n

How is any of this fun?

\n

Conspicuously Helpful and Painfully Kind

\n

WoW is a profoundly complex puzzle, and puzzles are meant to be solved. My first week of grind in Vanilla was vastly more satisfying than my first week over a decade ago because I intimately know how this puzzle is constructed. I rolled a hunter because I am clear how a hunter works. I’m ranged damage, I need a pet as quickly as possible because my melee attack sucks, and I need to become adept at managing that pet so that I don’t die so I can avoid long ghost walks.

\n

I am space constrained. Bags are worth their weight in gold, but I have no gold. I have bronze coins which with a ton of work will become silver coins which eventually will become gold coins. I’m going to need 100 of these hard to earn gold coins when I hit level 40, so I can purchase a mount and travel efficiently. I’m level 18 now. I have 22 silver and 81 bronze. I am forever away from 100 gold coins.

\n

And then, standing there in the rain in Darkshore, a random Dwarf walks up next to me, opens the trade window, and gives me a bag. For free. No commentary, no requests, just a free bag in the middle of the forest. I bow. He walks off.

\n

Oh right…

\n

Now, I’m standing outside of this cave, and it’s a cave full of quest-necessary mushrooms protected by fish-like creatures named Naga. I remember this cave from years ago because I spent hours of my life ghost walking back to this cave after being swamped by Naga. I did not know what I know now that just about everything is easier with others in WoW. Social connections are encouraged, so the moment I see a friendly player I request to join forces, and they accept. We competently charge into the cave and quickly overpower the Naga.

\n

Oh, right… people are helpful in this game.

\n

When it became clear to me the I was going to play Vanilla, I searched the internet from remnants of my old guild, Liquid Courage because those humans were why I devoted time to this game. A bunch of gaming strangers building a helpful community. The reason the ten-year-old game was so familiar to me was because of those helpful mostly anonymous humans who took the time to explain how it worked, how to optimize my play, and how to work together to solve this punishing grind.

\n

Oh, right… people are helpful in this game. The community is what made this game great. Conspicuously helpful and painfully kind.

\n

Darkshore is a Place… To Me

\n

Liquid Courage is still a guild on Cenarius, one of the many realms on the latest World of Warcraft. I recently logged in quietly to see familiar names there. I found the guild on Facebook, too. Familiar names were now playing with children who were yet born the last time we charged into Zul’Gurab seeking the heart of Hakkar.

\n

When I started playing WoW, there was no rain. It was an effect that was added in a later patch. I do remember the moment I first saw and heard the rain. I wasn’t in Darkshore; I was in the Arathi Highlands – a different continent. I don’t remember if I was level 60, yet, but I remember when it started raining. I was questing with a guild-members. Did we need spiders? Maybe?

\n

It just started raining, and we celebrated the digital rain.

"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"keywords":["Advice","Development","Opinion"],"originId":"https://furbo.org/?p=2411","fingerprint":"1953a570","id":"6IpDVPJg6aqZjbDgAeUFNoin2kWUje5qGrfJkJShV7Q=_16d81299bad:4da11:18991ffa","author":"Craig Hockenberry","summary":{"direction":"ltr","content":"Luckily, I don’t have to use this kind of title often. But when I do, there’s a good reason: this year’s beta release cycle for all of Apple’s operating systems has been a mess. The months since WWDC in June have been a terrible experience for both customers and developers alike and the literal center […]"},"alternate":[{"href":"https://furbo.org/2019/09/04/icloud-clusterfuck/","type":"text/html"}],"crawled":1569830050733,"title":"iCloud Clusterfuck","published":1567623053000,"origin":{"streamId":"feed/http://furbo.org/feed/","htmlUrl":"https://furbo.org","title":"furbo.org"},"content":{"direction":"ltr","content":"

Luckily, I don’t have to use this kind of title often. But when I do, there’s a good reason: this year’s beta release cycle for all of Apple’s operating systems has been a mess. The months since WWDC in June have been a terrible experience for both customers and developers alike and the literal center of the chaos was Apple’s iCloud syncing service.

\n

For us, it all started with customers reporting lost Linea sketches in their iCloud Drive. Initial investigations led to a common factor: all of the people affected had installed the iOS 13 beta release.

\n

And when I say lost, I mean really lost. Entire folders were either gone or corrupted. Apple’s mechanism to recover deleted files was of no help. The customers with weird folder duplicates were the “lucky” ones.

\n

We couldn’t find any problems in our code and there was no information from Apple about problems with iCloud, so we did the only thing possible: we blocked people from using Linea on iOS 13. If a customer absolutely needed the app, we got them onto TestFlight after a stern warning about the real possibility of losing data.

\n

A few weeks later, Apple finally indicated that there were some issues with iCloud and the beta release. In the same week, they released a public beta and sent out an email to customers encouraging them to try out iOS 13.

\n

We did our best to understand the situation and provide information to Apple, but it felt like we were tossing bug reports into a black hole. The most discouraging part was when we tried to open an incident with Apple Developer Technical Support (DTS). After writing up a detailed report, we were informed that they don’t support beta releases!

\n

By the time beta 6 rolled around, things were improving, but there were still isolated reports that the service might not be completely back in working order. Confirming the fixes on our end was impossible: the folks who’d encountered the previous data loss had no desire to mess with iCloud again.

\n

Now it appears that the entire stack is getting rolled back and there won’t be new iCloud features in iOS 13 (at least initially.) I honestly think that’s the wisest course of action at this point. My only wish is that Apple would make an official statement.

\n

Now that we’re past the worst of it, it’s time to think about why this whole episode happened and how it can be prevented in the future.

\n

Folks don’t understand beta

\n

Apple’s biggest fuck up was a bad assumption about who is testing a beta release. In WWDC presentations and developer documentation, there’s always warnings about using the release on non-production devices and only with test accounts.

\n

But there are many folks that are just looking to get the new and shiny features. In past iOS beta releases, Apple hasn’t suffered too much from this because the early software was relatively stable. Maybe you got some dropped calls or bad battery life, but it was nothing too serious.

\n

These early adopters installed iOS 13 and expected a similar experience. They also weren’t using an iCloud test account, so any instability in the beta release propagated bad data to their other devices.

\n

Developers have long known to unhook external drives when testing a new OS release. Shit happens, and that’s OK because it’s a beta and we expect a bumpy road. In fact, I currently have an external Photos Library that I can’t use in Mojave because it got upgraded when I booted into Catalina: it’s my dumb mistake and I have no complaints.

\n

Anyone who’s not a developer, and hasn’t been burned by a bad OS, does not know the kind of trouble that lies ahead. It’s irresponsible for Apple to release a public beta with known issues in iCloud. It’s doubly egregious to then promote that release with an email campaign to customers. For a company that prides itself in presenting a unified front, it sure looks like the left hand doesn’t know what the right hand is doing.

\n

Disconnect iCloud by default

\n

The solution to this situation is relatively simple, but with painful consequences. Apple needs to help unwitting customers by automatically disconnecting that external hard drive called iCloud.

\n

If a device is using an Apple ID that’s also being used on a non-beta device, then iCloud shouldn’t be allowed. If you install an iOS beta on your iPad, it doesn’t get to use any cloud services because it puts the data on your iPhone or Mac at risk.

\n

Of course, once this restriction is put into place, Apple will quickly realize that no one is testing iCloud or anything that touches it. If a beta tester can’t have their contacts, synced notes, reminders, and appointments over the course of three months, they’re not going to be testing much. I’d also expect to see lots of guides on “How to Get Back on Normal iOS” around the web.

\n

But there’s a better, and even more unlikely solution…

\n

iCloud can’t be a beta

\n

Because it’s a service, iCloud doesn’t get to go into beta. It needs to be reliable all the time, regardless of whether iOS or any other platform is in beta test.

\n

As it is now, Apple is effectively telling you that your storage device will be unreliable for a few months. It’s like having a hard drive where the manufacturer tells you it won’t work well for ¼ of the year. Would you purchase storage with a caveat that “the drive mechanism may not work properly during the hot summer months”?

\n

You don’t see these kinds of issues with Google Drive or Dropbox because they don’t get a pass while an OS is being tested. Dropbox moved your folders from AWS to their own servers without you noticing even the slightest hiccup. Compare this to the last three months of iCloud in beta.

\n

And yes this is a hard task: akin to swapping out the engines of an airplane in mid-flight. But data services, like hard drives, must work all the time.

\n

We’re well past that point with iCloud and it’s just going to get worse as Apple moves more of our data there. This time next year, will I suddenly not know which episodes I’ve watched on Apple TV? Or will I suddenly lose all my scores and achievements in Apple Arcade? Will I shoot myself in the foot with some cool new feature in iOS 14?

\n

Unfortunately for Apple, breaking iCloud away from the OS isn’t a simple problem to solve. It’s not a technical challenge as much as it is an organizational one: a move from a functional form to a divisional one.

\n

So while we all dream of an OS release roadmap to ease the transition to new features, Apple needs to give some very close to attention to the underlying services that would enable that new way of working.

\n

The lack of substantive communication about iCloud’s problems this summer shows how far the company has to go in that regard. You can’t use a roadmap without letting people know about your successes and failures. There will be wrong turns, and you can’t continue on in silence when people depend on the service’s reliability.

\n

iCloud gives data a bad name

\n

As a developer whose job requires frequent beta installations, this year’s problems with Apple’s service has brought something clearly into focus: as much as I love the functionality that iCloud provides, I cannot put my valuable data at risk.

\n

And you can bet your ass that I have a new backup strategy for anything that’s in iCloud Drive. (Hint: rsync and ~/Library/Mobile Documents.)

\n

As an Apple shareholder, I also worry about how these failures will damage the iCloud brand. Apple’s growth hinges on services and when they get a bad name, the stock price can only go one way. There is only one brand that people think of when you mention “click of death”.

\n

I sincerely hope that Apple can address these issues, because I love the idea of services that focus on simplicity and privacy. I know my last clusterfuck memo got some notice within the company, and I hope this one does as well.

"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"keywords":["Development"],"originId":"https://furbo.org/?p=2384","fingerprint":"6abb6e16","id":"6IpDVPJg6aqZjbDgAeUFNoin2kWUje5qGrfJkJShV7Q=_16d81299bad:4da10:18991ffa","author":"Craig Hockenberry","summary":{"direction":"ltr","content":"We recently started updating our macOS apps for Catalina: so far there have been very few issues with APIs and frameworks. The biggest hurdle has been the new notarization process that’s required for apps signed with a Developer ID: customers will be unable to download and launch your product easily until this step is completed. […]"},"alternate":[{"href":"https://furbo.org/2019/08/16/catalina-app-notarization-and-sparkle/","type":"text/html"}],"crawled":1569830050733,"title":"Catalina, App Notarization, and Sparkle","published":1565997404000,"origin":{"streamId":"feed/http://furbo.org/feed/","htmlUrl":"https://furbo.org","title":"furbo.org"},"content":{"direction":"ltr","content":"

We recently started updating our macOS apps for Catalina: so far there have been very few issues with APIs and frameworks. The biggest hurdle has been the new notarization process that’s required for apps signed with a Developer ID: customers will be unable to download and launch your product easily until this step is completed.

\n

Notarization involves an extra step in your build process: you upload an archived binary to Apple’s server with Xcode’s Organizer window and a short time later, you can export the binary. If you’ve automated your build process, you’ll need to make changes to your scripts to accommodate this new manual step. Apple’s documentation explains the process well.

\n

Before you can notarize the app, you’ll need to enable the hardened runtime in the target’s Capabilities panel. After flipping the switch you’ll see a array of exceptions and access permissions. You’ll want to survey this list carefully: for one product we needed Apple Events, for another Location was required.

\n

Things start to get tricky when you go to upload the binary: if you’re using Sparkle, it’s probably been codesigned without the hardened runtime, so you’ll immediately see an error.

\n

Sparkle Without a Sandbox

\n

How you deal with this error depends on which of the Sparkle versions you’re using. If your app isn’t sandboxed, your life will be a bit simpler because there are fewer things you’ll need to sign manually.

\n

After the target’s Copy Files build phase where the Sparkle.framework is moved into the application package, you’ll need to create a new Run Script step: I called ours “Sign Frameworks”. The script looks like this:

\n
\nLOCATION="${BUILT_PRODUCTS_DIR}"/"${FRAMEWORKS_FOLDER_PATH}"\nIDENTITY=${EXPANDED_CODE_SIGN_IDENTITY_NAME}\n\ncodesign --verbose --force --deep -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A/Resources/AutoUpdate.app"\ncodesign --verbose --force -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A"\n
\n

The key part in this step is the -o runtime. The codesign manual page describes this flag as:

\n

\nOn macOS versions >= 10.14.0, opts signed processes into a hardened runtime environment which includes runtime code signing enforcement, library validation, hard, kill, and debugging restrictions. These restrictions can be selectively relaxed via entitlements. Note: macOS versions older than 10.14.0 ignore the presence of this flag in the code signature.\n

\n

The good news here is that the build changes we’re making won’t affect your app when it runs on an older version of macOS.

\n

Sparkle in a Sandbox

\n

If your macOS app is in a sandbox, you’ll be using the version that relies on XPC services to perform the update. Like everything else in your application package, these will need to be signed correctly before you can submit your app for notarization.

\n

The “Sign Frameworks” build phase should look like this:

\n
\nLOCATION="${BUILT_PRODUCTS_DIR}"/"${FRAMEWORKS_FOLDER_PATH}"\nIDENTITY="${EXPANDED_CODE_SIGN_IDENTITY}"\n\ncodesign --verbose --force -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A/Resources/AutoUpdate"\ncodesign --verbose --force --deep -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A/Resources/Updater.app"\ncodesign --verbose --force -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A"\n
\n

You’ll also add a new Run Script build phase just before the XPC Services are embedded in your application package. Since you’ll only need to do this for release builds, the script looks like this:

\n
\nif [ "${CONFIGURATION}" = "Release" ]; then\n    $PROJECT_DIR/Sparkle/bin/codesign_xpc "Developer ID Application" $BUILT_PRODUCTS_DIR/*.xpc\nfi\n
\n

But wait, we’re not done yet! You’ll also need to update the codesign_xpc Python script with the -o runtime flag. It looks like this when you’re done:

\n
\ndef _codesign_service(identity, path, entitlements_path=None):\n    command = ["codesign", "-f", "-o", "runtime", "-s", identity, path] + ([] if entitlements_path is None else ["--entitlements", entitlements_path])\n    log_message(" ".join(map(sanitize, command)))\n    ...\n
\n

You’re Not Done Yet

\n

At this point, you should be able to do a build where everything in your app is using the hardened runtime. It’s more likely that you’ve had some kind of issue along the way: this Apple document helped get me over the rough patches. (Thankfully, I didn’t have to write it this time around.)

\n

After the notarization upload completes, you’ll see “Uploaded to Apple” in the organizer, then after a few minutes you’ll get an email and Xcode notification that your app is “Ready to distribute”. In the righthand panel underneath “Distribute App”, you’ll see that the “Export Notarized App” button is enabled and can be used to place the signed package anywhere on your Mac for further processing.

\n

In our case, we had to split up the build scripts into two parts: previously we had a single script that did the build, signed it with the Developer ID, and then created an appcast. Sparkle’s XML file is now created with a separate script that also prepares the release to be checked into our repositories.

\n

One final note: these instructions are based on Xcode 10, which is currently the only development tool that can be used to submit an app for notarization or the Mac App Store. Before we figured that out, we found that Xcode 11 does a better job passing along the -o runtime flag during a framework’s Code Sign On Copy. It’s likely that all this work you just did will only be needed for a few months. Sigh.

"},"visual":{"url":"none"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"originId":"http://david-smith.org/blog/2019/09/26/moon-plus-plus","fingerprint":"d717143d","id":"37MS5KwinfzXRHFFH8onqsLrpe3pbr+gQeC/KCTSLiw=_16d81299335:4da0f:18991ffa","updated":1569510120000,"alternate":[{"href":"http://david-smith.org/blog/2019/09/26/moon-plus-plus/","type":"text/html"}],"crawled":1569830048565,"title":"Moon++, a better lunar complication","published":1569510120000,"origin":{"streamId":"feed/http://david-smith.org/atom.xml","htmlUrl":"http://david-smith.org/","title":"David Smith, Independent iOS Developer"},"content":{"direction":"ltr","content":"

This app started life as so many of my apps do…from a frustration that just got under my skin.

\n

I just received my new Series 5 Apple Watch. While reading reviews for it, many reviewers were using the moon complication in their product shots. I think this is because it is the default complication for the new California watch face. Anyway, I liked the overall aesthetic of it and so tried it out myself.

\n

I went outside and looked up at the moon in the sky. This is what I saw:

\n

\n

Then I looked down at my wrist and saw this:

\n

\n

This bothered me more than it should. The two don’t really look the same at all. On my wrist is a powerful computer, capable of precisely locating itself on the surface of the earth (and indeed the solar system!)…but it isn’t showing me the correct moon.

\n

So I had to fix it…

\n

\n

The result is Moon++.

\n

Moon++ seeks to provide a highly accurate, visually pleasing indication of what the moon looks like right now, right where you are. My goal was to make it so that if you look down at your wrist and then up into the sky the images you see should match.

\n

Building it has been a fun little adventure into astronomy. The moon image I generate is adjusting itself for all manner of astronomical factors. I got to learn about such fun terms as: parallactic angle, libration, and lunar terminator.

\n

\n

It includes a robust set of complications for every watch face type. You can choose between either a visually rich ‘actual’ appearance or a more simplified version, to fit your own personal style.

\n

\n

Within the actual app itself it displays the current location of the moon in the sky (azimuth and elevation) along the horizon. If you rotate the Digital Crown you can shift forward and backwards through time.

\n

Additionally, on the new Series 5 Apple Watch, which is equipped with a compass, you can have Moon++ orient itself towards the actual moon to make it easier to locate the moon in the sky.

\n

I hope you enjoy this little utility, I really enjoyed making it.

\n

It is $0.99 in the App Store.

\n
\n

A Press Kit for the app is here.

\n
\n

Technical Note:

\n

This is my first app built using SwiftUI. It is really cool to see how interactive and lively you can now make an Apple Watch app. The compass mode feature especially wouldn’t really have been practical or performant to build using WatchKit.

\n

Also, it is an Apple Watch only app, which means I didn’t have to build a hosting iPhone app.

\n

»"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/galaxygear-lead.jpg","width":619,"height":411,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"originId":"http://david-smith.org/blog/2019/09/13/seamless-light-complications","fingerprint":"cd8567d7","id":"37MS5KwinfzXRHFFH8onqsLrpe3pbr+gQeC/KCTSLiw=_16d81299335:4da0e:18991ffa","updated":1568388600000,"alternate":[{"href":"http://david-smith.org/blog/2019/09/13/seamless-light-complications/","type":"text/html"}],"crawled":1569830048565,"title":"Seamless Light Complications","published":1568388600000,"origin":{"streamId":"feed/http://david-smith.org/atom.xml","htmlUrl":"http://david-smith.org/","title":"David Smith, Independent iOS Developer"},"content":{"direction":"ltr","content":"

I really like the new Meridian face that arrived in the watchOS 6 GM. It is clean, simple but still provides ample opportunity for customization. Over the summer I’ve been rocking the California face (with Arabic numerals because its default mixed Roman numerals hurt both my head and heart). But the California face only has extensive complication options in the corners which isn’t my preferences. I like the face itself going to the edge of the display, it is one of the things I like most about the Series 4/5 display, that it really fills the face.

\n

There is, however, one thing I’m now really wishing for with the Meridian face…the ability to make seamless complications for its light face options.

\n

For the dark face variants, we already have this. You have white text on a black background that blends seamlessly into the background of the watch.

\n

\n

Increasingly Apple is providing options for us to have non-black watch faces, which I think creates a delightful bit of character and delight for the Apple Watch. These started with the Infograph faces and now continue with both California and Meridian. However, sadly we don’t yet have a way to make complications that can blend seamlessly into the these light colored backgrounds. The best I can currently do is something that looks kinda like this:

\n

\n

Which isn’t awful, but really interrupts the full effect that would be possible if the complication blended seamlessly into the background. Apple already allows this for the “Digital Time” complication so I know it isn’t completely out of the question. Here is an altered version of the above screenshot removing the ring around my complications.

\n

\n

Doesn’t that look great!

\n

So this is my little feature request for apple (filed as FB7264371), I’d love to see a way to provide seamless complications for these colored backgrounds. Fingers crossed!

\n

Note: The font used in my custom date and weather complications is Redbird, which is my current favorite for display on an Apple Watch face. It just looks at home on this device.

\n

»"},"visual":{"url":"http://www.blogcdn.com/www.engadget.com/media/2013/10/galaxygear-lead.jpg","width":619,"height":411,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"originId":"http://www.caseyliss.com/2019/9/19/stalman-podcast","fingerprint":"a907e8ac","id":"fkFLg490GSKODJZCYxuxuSDw5utmxlHxrMJQGyyxmh0=_16d8129898d:4da0d:18991ffa","author":"Casey Liss","summary":{"direction":"ltr","content":"

Last night I recorded a podcast with my pal Tyler Stalman on his\nshow, The Stalman Podcast. Tyler received review units of the\niPhone 11, iPhone 11 Pro, and Apple Watch Series 5. After a whirlwind\nday of him testing the phones, Tyler made some time to sit down with\nme to discuss them.

\n

In this episode, I turned the tables on Tyler and interviewed\nhim. As a professional photographer, Tyler naturally had a lot of\nthoughts about the new two-camera system on the 11 and the three-camera\nsystem on the 11 Pro.

\n

I had a ton of fun recording this with Tyler, and for those of you\nwaiting in line tomorrow, or debating buying a new iPhone, this episode\nis for you.

\n

Read on Liss is More

"},"alternate":[{"href":"https://www.stalmanpodcast.com/64","type":"text/html"}],"crawled":1569830046093,"title":"Appearance: Stalman Podcast 64","published":1568942100000,"origin":{"streamId":"feed/http://www.caseyliss.com/rss","htmlUrl":"https://www.caseyliss.com","title":"Liss is More"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/06e5FJWgUfUSmDaPJIEZoGF1XOs=/0x68:2040x1136/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/10378819/DSCF3031.jpg","width":1200,"height":628,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"originId":"http://www.caseyliss.com/2019/8/27/family-feud","fingerprint":"8d3ef9eb","id":"fkFLg490GSKODJZCYxuxuSDw5utmxlHxrMJQGyyxmh0=_16d8129898d:4da0c:18991ffa","author":"Casey Liss","summary":{"direction":"ltr","content":"

Just over five years ago, Myke and I announced Analog(ue). It doesn’t\nfeel like it was that long ago, but here we are. Analog(ue) is older than both\nof my children; Myke and I have released 163 episodes of the show. Not bad for\nsomething we thought would peter out after ten or twenty episodes.

\n

To celebrate Analog(ue)'s Relay’s fifth anniversary, over twenty hosts\njoined Myke and Stephen in San Francisco to record a live game of\nFamily Feud. It was a blast, and probably the zaniest podcast I’ve\never been a part of.

\n

The whole episode is up on the Connected feed, if you’d like to have a\nlisten. There may be video in the future, but no guarantees. If there is, I’ll\nlink it here.

\n

Read on Liss is More

"},"alternate":[{"href":"https://www.relay.fm/connected/257","type":"text/html"}],"crawled":1569830046093,"title":"Appearance: Connected 257","published":1566916200000,"origin":{"streamId":"feed/http://www.caseyliss.com/rss","htmlUrl":"https://www.caseyliss.com","title":"Liss is More"},"visual":{"url":"https://cdn.vox-cdn.com/thumbor/06e5FJWgUfUSmDaPJIEZoGF1XOs=/0x68:2040x1136/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/10378819/DSCF3031.jpg","width":1200,"height":628,"contentType":"image/jpeg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"originId":"https://allenpike.com/2019/multiply-your-time","fingerprint":"99105153","id":"r0GuC4sgAzEOyudB9cuXyJaa1HYa9lXs9s0Sf3Zd858=_16d8128aefe:4da03:18991ffa","updated":1567278610000,"author":"Allen Pike","alternate":[{"href":"https://www.allenpike.com/2019/multiply-your-time","type":"text/html"}],"crawled":1569829990142,"title":"Multiply Your Time","published":1567278610000,"origin":{"streamId":"feed/http://www.allenpike.com/feed/","htmlUrl":"https://www.allenpike.com/","title":"Allen Pike"},"content":{"direction":"ltr","content":"

One of my most hated things is when someone over-fills a garbage bag.

\n

You see, an almost-full garbage bag is a small task to deal with – you just close it up and pull it out. But an over-full garbage bag is a problem. Suddenly, you can’t just pull it closed anymore – dealing with the bin now involves biohazardous juggling, something nobody is inclined to do anytime soon. The resulting additional procrastination leads to, in most cases, an unstoppable garbage snowball that eventually destroys humanity itself.

\n

Everybody has their irrational pet peeves. As long as I can remember, this has been one of mine. For years I always tried to prioritize the task of emptying the bin before it got over-filled. Sometimes I succeeded, but other times I failed, and a stupid garbage can would put me on tilt.

\n

One day, when I attempted the task of emptying the bin, I found it over-full. And instead of emptying it, I left. I left for Home Depot, on a different task that would change how I thought about prioritization forever: I bought a smaller garbage bin.

\n

\n

It may be obvious to you, but for years I ignored the root cause of my age-long grief. I did nothing about the thing that actually enabled people to pile in more garbage than the bag could hold: the bin was bigger than its bag. By buying a smaller bin, the same bags simply couldn’t be over-filled. Even the most precariously over-filled bin could be wrapped up swiftly and neatly, using the specially reserved portion of bag draped on the outside of the bin, ready and waiting for duty.

\n

It was glorious.

\n

The quietly worthwhile

\n

There is a lot of stuff you should get done.

\n

Or, more accurately, there is a lot of stuff you feel like you should get done. My OmniFocus tracks 860 actions that, at one time, I felt like I should do. Many of them won’t get done. But there are some things in there that I truly should do, and if I do them, I’ll be very glad.

\n

The question is: which ones?

\n

Some actually-important things are urgent. If you don’t renew your passport, you can’t go on your upcoming trip. If you don’t empty the garbage before garbage day, you’ll be stuck with old garbage for two weeks. The task needs to get done, you need to do it, so you just gotta do it. It’s kind of obvious, because it’s both urgent and important.

\n

More interesting, though, is the non-urgent stuff that is nevertheless very worthwhile. The tasks that are easy to defer, tempting to procrastinate, but actually more important than the supposedly urgent tasks: the smaller garbage bins, waiting to be bought.

\n

So, when I’m looking at things I should do, I keep an eye out for certain kinds of tasks: work that isn’t urgent, but can multiply time. Things that could pay off for months or years to come.

\n

In particular, I try to consider if I can:

\n
    \n
  1. Automate. Can you spend 1 hour simplifying or automating something that would then save yourself 5 minutes a week? If so, then I have an incredible investment opportunity for you: you can invest time – the most precious resource we have – at a 430% annual interest rate. The busier I get, the harder it is to get around to tackling these automation and simplification tasks, but the more worthwhile it is.
  2. \n
  3. Teach. If there’s somebody who’d be willing to do this task in the future, but they don’t know how yet, then you have a big opportunity. The long-term payoff for teaching someone how to do a task can be massive. Even in the case of successfully handing something off only for it to “boomerang” back later, having taught still improves your understanding of the work and makes you a better teacher for the next attempt. If you’re in a leadership position of any kind, you don’t have the time not to be teaching people.
  4. \n
  5. Calm. Like many people, I have a certain amount of tolerance for frustration and stress in a given day. At a certain point further annoyances, even small ones, cause disproportionate reactions and sap energy from me and those around me. That’s why I find it useful to prioritize fixing anything that is a persistent aggravation or stressor. In OmniFocus, I keep an ongoing project “Make Life Calmer”. Doing work today that will make tomorrow calmer and more focused is a great investment, and will let you use your future “stress budget” on something more meaningful than the fact the damn kitchen drawer is stuck closed again because the saran wrap and aluminium foil boxes got pushed on top of each other again and god DAMN it why do I have three different sets of measuring cups in here augghhhh.
  6. \n
  7. Improve. It is extremely easy to procrastinate making yourself better. It takes motivation to build new habits, learn how do to something “right”, or to address a longstanding problem. But this work is profoundly worthwhile, since small investments here can have huge payoffs. After 35 years of using a computer like a dumb animal, I’m finally learning how to sit, type, and work in a way that doesn’t permanently injure my wrists and shoulders. Reading books about RSI or learning better posture aren’t necessarily my idea of fun, but I’m going to benefit from the investment for decades to come.
  8. \n
\n

Diamonds in the rough

\n

Next time you’re cleaning up your todos, considering a new goal or theme, or just feeling over-busy, consider how you can be multiplying your time. What things, once done, will have an impact that pays off for years?

\n

What is your smaller garbage bin?

"},"visual":{"url":"http://cdn0.sbnation.com/entry_photo_images/9193021/3462607995_150a6b2624_z_large.jpg","width":630,"height":420,"contentType":"image/jpg"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"K2yEyJkJAlTYIjP5i2D9ccp/wbMYzpHnabsacInQgcs=_16d7e95f988:1fc4:90d684ff","keywords":["digital-publishing","newspapers","france","search-engine-optimizati","media"],"originId":"https://medium.com/p/948b24fd555c","fingerprint":"ea8aeae3","content":{"content":"

#MN_Briefing 09.29.19

by Frederic Filloux

\"\"
Google responding to the French law on snippets

In today’s briefing: Google on the warpath with French publishers | Le Monde facing crucial choices for its future | The New York Media/Vox deal | Some news about Deepnews | Great long forms.

1. Google fires a warning shot at EU publishers

TThe Facts. Google chose the hard way when faced with the decision on how to comply with France’s new copyright law, set to come into force in late October. The law states that publishers are entitled to be paid by online services that display snippets of articles. It is the result of intense pressure on behalf of French publishers.

Last Wednesday, Google announced that, in order to be fully compliant with the new French law, it will simply stop showing snippets below headlines, as well as thumbnails.

Here is an abstract of Google’s statement (full text in FR and ENG here), emphasis mine:

At the moment, when we display news results, we show a headline, which links directly to the relevant news site. For some results, we also show you a short preview of the article, such as a few lines of text (also known as a “snippet”) or a small “thumbnail” image. Together, these headlines and previews can help you decide whether a result is relevant to your search, and whether you want to click on it.
When the French law comes into force, we will not show preview content in France for a European news publication unless the publisher has taken steps to tell us that’s what they want. This applies to search results across Google services.

Why it matters.
1) French publishers have a lot to lose in the process.
To understand the visible impact of Google’s decision, here is the same set of news, collected yesterday morning with a simple query “Jacques Chirac” (the former French President who died last week), in Google search. Assuming none of the publishers mentioned below will take the steps to opt-in back to the former system of snippets (or snippets + thumbnail), the impact will be significant:

\"\"

Maintaining the volume of traffic sent back to publishers from search results will require a united front in which no one publisher accepts going back to the headline + snippet system, and/or the adoption of clear and straightforward headlines. Otherwise, the loss in traffic could be significant.

2) Google’s reaction equals to a shot across the bow of EU publishers: It says: “Do not push your governments to pass such kinds of law, or you will face the consequences”. Google’s position has the merit of consistency. Over the last five years, the search giant stood by its tune: “We won’t pay for snippets. Ever”. The news should not, therefore, come as a surprise for the French publishers. But, as an industry addicted to subsidies, supposedly protected by the EU as the Daddy and the French government as the Mummy, they hoped for a miracle which didn’t happen.

The lingering questions that I will cover in a future Monday Note:

1 . How French publishers will react to this, besides besieging the Ministry of Culture, who already slammed Google’s decision as unacceptable (press release in FR here)?

2 . It varies from one publisher to another, but Google News and Google Search might count for as much as 30 percent of the traffic a news site is getting. French publishers will still be present in SERPs (Search Engine Return Pages), but, as a default setting, their presence will be reduced. What will be the impact on their traffic? How many will opt-in to go back to the original system? Will they remain united? (Of course not).

3 . Is there some room for negotiations between Google on one side and the French government and the publishers on the other?

4 . Why does the issue of paid-for-snippets seem to be so specific to the EU and more specifically French publishers?

5 . If we take into account, on one hand, the value of the clicks sent to the publishers + the amount of funding direct and indirect injected by Google into the news sector (hundreds of millions of dollars), and on the other hand, the benefits for Google of inserting news elements in the SERPs, is the news industry really at a disadvantage?

Background: Past Monday Notes that cover the issues:

2. Follow-up on Le Monde’s saga: tough choices, hard consequences

LThe apparent good news: all shareholders of Le Monde agreed last week to sign a pact that will give to the staff’s representatives the right to veto an unwanted investor. At the same stroke, they decided to “consider the possibility” of a foundation. So far, only Xavier Niel, the telecom mogul who calls the shots here, is pushing in this direction. Despite an acceptable write-off, he will appear as the savior of Le Monde. Other stakeholders are not on board: Matthieu Pigasse, who narrowly escaped a personal bankruptcy, is about to make a good deal; Madison Cox, who manages Pierre Bergé estate, wants to sell his shares for a profit, and the newcomer, Daniel Kretinsky, who teamed up with Pigasse, is a patient man with large ambition.

Why it matters. Le Monde is facing a crucial moment in its existence. Nine years ago, the paper was on the verge of bankruptcy. Today, it harbors good journalism, even occasional strokes of extraordinary reporting. Financially speaking, Groupe Le Monde is well-managed but faces an uncertain/negative outlook for the future, as all insufficiently diversified media companies do.

Now the choice is this:

a) Scenario A: Le Monde engages in a decisive development strategy with a comprehensive investment in technology to improve the performances of its core assets (the newspaper and magazines) and opts for diversification with acquisitions in services and e-commerce. The goal would be to double its current €300 million revenue in a few years, expand its footprint in France and maybe in Europe. That’s a realistic option, which can’t be done with an ownership structure that is leaning toward a non-profit.

b) Scenario B: Le Monde folds itself in a foundation-like system, fencing its ownership for good. The short-term will be nice: predatory investors will be kept at bay, management will be groomed inside the company — no one from the outside world will dare to compete for a CEO or an executive editor position knowing that they can be vetoed by the kolkhoz. It will be like a global airline deciding that it will never seek talent from abroad. If the fundamentals are fine, the company will remain within its cozy perimeter with a decaying genetic talent pool and a business that barely survives.

But most likely, fundamentals will degrade, with a continual depletion on the advertising side, a subscription fatigue and a market penetration that will plateau (unlike any English-speaking media which enjoys a growing global pool of educated readers). In less than ten years, Le Monde will be back to begging, with a déjà vu “bail-me-out-or-I-die” plea.

Background: Past Monday Notes on the subject:

3. New York Magazine + Vox: a good deal, by the numbers

NA great, iconic magazine combined with a powerhouse in digital news: that’s the New York Magazine and Vox Media deal. Altogether, they will operate 14 digital properties with a combined reach of 125 million unique visitors:

\"\"

The all-stock deal values New York Media LLC at $105 million, a nice markup compared to the $55 million paid by the late famous investor Bruce Wasserstein in 2004 when he bought the company from Rupert Murdoch. According to the WSJ, NY Media’s revenue grew by 40% in the last 12 months but it lost $15 million in 2018. The magazine has been particularly good at segmenting its digital audience over several, remarkably efficient, entities. New York Media is getting 12 percent of the new venture that will retain the name of Vox.

\"\"

As for Vox Media, it is valued at $750 million in the operation, a severe discount compared to a valuation of $1 billion in 2015. It makes a small profit on a $185 million revenue. It too has a segmented a valuable audience (78 million UVs). It is a super agile company, which built is own CMS (Chorus) and its own advertising production studio.

Why it matters. Despite its 44 magazine awards, the 51-year-old NY Mag would not have been attractive for a young digital native company if it hadn’t been for its profound modernization and sizable investment in digital operations. The Vox/NY Mag merger is the perfect proof that the magazine industry is not doomed.

4. My long forms of the week

Two great pieces from the New Yorker I read during a trip to Germany:

Can a Burger Help Solve Climate Change?
Eating meat creates huge environmental costs. Impossible Foods thinks it has a solution.
This piece is just incredible in its thoroughness, density, precision as well as its narrative structure. I take it as an example in my journalism classes.

Paging Dr. Robot.
A pathbreaking surgeon prefers to do his cutting by remote control.
I have been following Intuitive Surgical for a long time. The firm makes the extraordinarily complex DaVinci robot — which is, for a large part, the future of medicine. This piece is the best on the topic.

5. News about Deepnews

XTwenty issues of the Deepnews Digest have given us pretty good insight about the potential of lightweight, automated newsletters. Deepnews Digest is built on a different news-related topic every week. It gathers a large number of articles across 400 carefully vetted sources, with a final selection made by our proprietary algorithm, the Deepnews Scoring Model. The whole process is 95 percent automated, except for the writing of the introductory text and the removal of a few parasite links. Our excellent editor, Christopher Brennan, spends less than an hour each week to do the job.

Now, we want to develop a commercial version of the Deepnews Digest to cover specific sectors with a series of vertical, paid-for publications. The idea is to identify markets worth serving and to quickly build products, easy to test, launch and evaluate. Although we have an idea of where to go, we are quite interested in hearing from you about the sectors we should cover:
Please, take five minutes to complete this questionnaire.
Your opinion matters a lot to us.

See you next week.

frederic.filloux@mondaynote.com

\"\"

#MN_Briefing 09.30.19 was originally published in Monday Note on Medium, where people are continuing the conversation by highlighting and responding to this story.

","direction":"ltr"},"title":"#MN_Briefing 09.30.19","updated":1569786960272,"author":"Frederic Filloux","alternate":[{"href":"https://mondaynote.com/mn-briefing-09-30-19-948b24fd555c?source=rss----c537d80ed0a---4","type":"text/html"}],"crawled":1569786821000,"published":1569786821000,"origin":{"streamId":"feed/http://www.mondaynote.com/feed/","title":"Monday Note - Medium","htmlUrl":"https://mondaynote.com?source=rss----c537d80ed0a---4"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"qxI9mPUQaH2N8pL2LRpLlzIW5hw1iiQKLdJmLkGau/I=_16d7e4a6db0:1f34:90d684ff","keywords":["User Interface","VR/AR"],"originId":"http://jnack.com/blog/?p=9310","fingerprint":"15a6ef0f","content":{"content":"

Looks lit.

\n

\n

(Tangential reminder: You can build hand tracking into your mobile app right now using tech from my team.)

\n

\"NewImage\"

\n

[YouTube]

","direction":"ltr"},"title":"Oculus Quest adds hand tracking","author":"jnack","summary":{"content":"Looks lit. (Tangential reminder: You can build hand tracking into your mobile app right now using tech from my team.) [YouTube]","direction":"ltr"},"alternate":[{"href":"http://jnack.com/blog/2019/09/29/oculus-quest-adds-hand-tracking/","type":"text/html"}],"crawled":1569781870000,"published":1569781870000,"origin":{"streamId":"feed/http://jnack.com/blog/?feed=rss2","title":"Nackblog","htmlUrl":"http://jnack.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"K2yEyJkJAlTYIjP5i2D9ccp/wbMYzpHnabsacInQgcs=_16d7d29c728:1fc3:90d684ff","keywords":["tesla"],"originId":"https://medium.com/p/29fd3b55c5b4","fingerprint":"e29fa3bb","content":{"content":"

by Jean-Louis Gassée

With its just-announced Taycan 4-door sedan, Porsche got tongues wagging. Ultra-fast at the Nürburgring, ultra-refined — and ultra-expensive — it’s a clever entry in the EV race, one that some see as a threat to Tesla.

\"\"
Courtesy Wikipedia

Ending months of speculation, Porsche picked Germany’s Frankfurt Auto Show (officially, the International Motor Show) to unveil the Taycan, the automaker’s entry in the Electric Vehicle race. To make the Taycan’s unveiling even more print- and click-worthy, weeks before the Frankfurt intro, Porsche headed to the famed Nürburgring track where the Taycan broke the 4-door EV record (video inside) with a 7 minute, 42 second lap.

For Monday Notes readers who aren’t into carnography, the legendary 20.8 km (12.9 mi) Nürburgring track has become a reference venue where automakers and tourist drivers attempt to break speed records in various categories.

(A brief historic digression: the first vehicle to break the 100km/h (62 mph) barrier was an electric car, yes, in 1899, La Jamais Contente (The Never Satisfied) managed to exceed 105km/h (65.8mph). This was the last time an EV drove faster than a gasoline-powered car.)

Before the Taycan, the Volkswagen ID R, a cousin EV in the Volkswagen Group, managed a 6:05 lap, an impressive feat but one that, unlike the Taycan’s, doesn’t belong in the Production Vehicles category. Earlier in the year, a modified ID R broke the Pikes Peak Hill Climb record.

The Volkswagen group, or VW AG, is a colossus that includes Porsche, Audi, Bugatti, and many others. It surpassed Toyota as the world’s largest auto maker by sales in 2016 and seems to have held the title since. By shattering EV speed records at historic venues, VW AG wants everyone to know that they’re not only big, they’re dead serious about their EVs, and about owning the performance crown in the category.

Furthermore, the giant German auto group has clearly decided to take the very high end of the production EV category. The Taycan starts at a theoretical $150K but, in Porsche tradition, it will easily exceed $200K in more desirable configurations.

For years, automakers have complained about the impossibility of generating any kind of profit by making EVs. Most of the models we’ve seen so far have either been “halo cars” meant to burnish a maker’s EV bona fides, or, as in the case of Fiat Chrysler electric Fiat 500, a way to gain CO2 emission credits. As an example, at the Frankfurt Auto Show mentioned above, Jaguar let it be known that its iPace had done its job and wouldn’t be iterated in the near term. BMW said very much the same about its carbon-fiber i3.

For “real” EVs, meaning models produced in hundreds of thousands or more per year, we must proceed with caution: Look at Tesla hemorrhaging $24B in free cashflow since 2011

With this in mind, one can look at Porsche’s Taycan as a doubly cautious move: a very expensive vehicle, produced in modest quantities. In 2018, Porsche sold about 250K cars, with the Macan and Cayenne leading the way with 86K and 71K units, respectively. With the evergreen 911 selling about 35K units, one could expect Taycan 2020 sales to be in a low 10K multiple. A fine addition to the very profitable Porsche business — but not an invasion.

Unsurprisingly, the kommentariat was prompt to position the Taycan as a Tesla rival, a killer, even. Elon Musk helped by announcing an attempt to outdo the Taycan’s Nürburgring’s time “any day now”, perhaps with a new three engine configuration. Rumors aside, nothing so far. No one should second guess Musk’s ability to promote the Tesla brand (and himself), nor should we underestimate the value of the Nürburgring crown, but a Tesla-killer the Taycan isn’t.

To start with, there’s scale. Give or take a few thousand in the end-of-quarter rushes, Tesla will approach 400K deliveries this year and get close or exceed 500K units in 2020 when the company’s Gigafactory 3 comes on line in China (assuming, of course, that the economy is willing and investors continue to feed cash into Tesla).

Second, Tesla and Porsche operate in different price ranges. The Model 3, Tesla’s best selling car, now costs less than $50K; the Model S is less than $100K and the Model X is a bit more. There’s a big jump up to the Taycan’s $150k+ minimum price tag, certainly far too large for a faithful Model 3 adherent, and probably too big for even a Model X owner.

That said, many Porschephile connoisseurs think the real Tesla killer will be an EV Macan. The gasoline powered Macan is a best-seller today, and could be a best-seller tomorrow as a new, designed-from-the-ground-up EV sporty SUV that will compete more directly with the still-gestating Model Y Tesla SUV.

But a more immediate threat to Tesla is likely to come from elsewhere in the VW AG product portfolio. At the Frankfurt show, Volkswagen announced the ID.3, a car that many see as the EV equivalent of the iconic Golf, a vehicle that managed to span 45 years in seven generations without losing its soul. Production of the ID.3 is slated to start this coming November with deliveries for existing reservations in the Summer of 2020, albeit limited to 30K units for the “1ST” edition (or so we think…it isn’t entirely clear). The price is likely to exceed 40K€ (close to $50K).

Stepping back a bit, while the ID.3 should offer some competition in the Model 3 price range, it isn’t yet a full-bore assault on Telsa. Today, the Model 3 is a top seller in several European countries and neither VW nor Porsche threaten this position for the near-to-medium term, meaning 2020. After next year, things could get interesting as EV models from other makers (Mercedes, BMW, Renault…) garner praise when compared to Tesla’s sometimes lackadaisical finish and inattention to creature comforts.

Unless, of course, Musk’s “million robotaxis” prediction comes true — but no one took him seriously, right?

— JLG@mondaynote.com


Porsche vs Tesla: False Equivalence was originally published in Monday Note on Medium, where people are continuing the conversation by highlighting and responding to this story.

","direction":"ltr"},"title":"Porsche vs Tesla: False Equivalence","updated":1569762953218,"author":"Jean-Louis Gassée","alternate":[{"href":"https://mondaynote.com/porsche-vs-tesla-false-equivalence-29fd3b55c5b4?source=rss----c537d80ed0a---4","type":"text/html"}],"crawled":1569762953000,"published":1569762953000,"origin":{"streamId":"feed/http://www.mondaynote.com/feed/","title":"Monday Note - Medium","htmlUrl":"https://mondaynote.com?source=rss----c537d80ed0a---4"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"qxI9mPUQaH2N8pL2LRpLlzIW5hw1iiQKLdJmLkGau/I=_16d7588ab10:1f33:90d684ff","keywords":["Miscellaneous"],"originId":"http://jnack.com/blog/?p=9304","fingerprint":"b1284979","content":{"content":"

Racks on racks on racks o’ cloud GPUs mean we’re setting up to deliver some awesome experiences. Check out these PM gigs:

\n\n

If you apply, please tell ‘em who sent ya. \"\uD83D\uDE0C\"

\n

\"NewImage\"

","direction":"ltr"},"title":"PMs: Come build cloud-based gaming at Google","author":"jnack","summary":{"content":"Racks on racks on racks o’ cloud GPUs mean we’re setting up to deliver some awesome experiences. Check out these PM gigs: Product Manager, Infrastructure, Stadia – Mountain View Product Manager, Graphics, Stadia – Waterloo or Munich If you apply, please tell ‘em who sent ya. \uD83D\uDE0C","direction":"ltr"},"alternate":[{"href":"http://jnack.com/blog/2019/09/27/pms-come-build-cloud-based-gaming-at-google/","type":"text/html"}],"crawled":1569634954000,"published":1569634954000,"origin":{"streamId":"feed/http://jnack.com/blog/?feed=rss2","title":"Nackblog","htmlUrl":"http://jnack.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d74088b20:205d:90d684ff","keywords":["Technology","iOS","iOS 13","Swift Programming Language"],"originId":"https://mjtsai.com/blog/?p=26732","fingerprint":"62a7d953","content":{"content":"

Alexandre Colucci (Hacker News):

\n
\n

The new iOS 13 features have with no surprise been built with some Swift code in their corresponding application. This is the case of the FindMy, Reminders and Sidecar apps. Also worth noting is the use of Swift in the Health, Book and Shortcuts apps.

\n

[…]

\n

If we exclude the Swift libraries, iOS 13.1 contains 141 binaries using Swift, more than doubling the number from iOS 12[…]

\n
\n

Previously:

\n","direction":"ltr"},"title":"Apple’s Use of Swift in iOS 13","author":"Michael Tsai","summary":{"content":"Alexandre Colucci (Hacker News): The new iOS 13 features have with no surprise been built with some Swift code in their corresponding application. This is the case of the FindMy, Reminders and Sidecar apps. Also worth noting is the use of Swift in the Health, Book and Shortcuts apps. […] If we exclude the Swift […]","direction":"ltr"},"alternate":[{"href":"https://mjtsai.com/blog/2019/09/27/apples-use-of-swift-in-ios-13/","type":"text/html"}],"crawled":1569609780000,"published":1569609780000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","htmlUrl":"https://mjtsai.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d74087798:205c:90d684ff","keywords":["Technology","Artificial Intelligence","Auto-Correction","iOS","iOS 13"],"originId":"https://mjtsai.com/blog/?p=26730","fingerprint":"7a0498ce","content":{"content":"

John Gruber:

\n
\n

One thing I and others have noticed is that when you type a dictionary word correctly — meaning you hit the exact right keys on the on-screen keyboard — iOS 13 autocorrect will replace it with a different dictionary word that makes no contextual sense. Even beyond dictionary words, I’m seeing really strange corrections.

\n
\n

I think this has been going on since before iOS 13.

\n

Previously:

\n","direction":"ltr"},"title":"iOS 13 Autocorrect Is Drunk","author":"Michael Tsai","summary":{"content":"John Gruber: One thing I and others have noticed is that when you type a dictionary word correctly — meaning you hit the exact right keys on the on-screen keyboard — iOS 13 autocorrect will replace it with a different dictionary word that makes no contextual sense. Even beyond dictionary words, I’m seeing really strange corrections. I think this […]","direction":"ltr"},"alternate":[{"href":"https://mjtsai.com/blog/2019/09/27/ios-13-autocorrect-is-drunk/","type":"text/html"}],"crawled":1569609775000,"published":1569609775000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","htmlUrl":"https://mjtsai.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d74084ca0:205b:90d684ff","keywords":["Technology","BlockBlock","Mac","Mac App","macOS 10.14 Mojave","Malware","Security"],"originId":"https://mjtsai.com/blog/?p=26728","fingerprint":"8d8dcbfc","content":{"content":"

Patrick Wardle (via Leo M):

\n
\n

Malware installs itself persistently, to ensure it’s automatically re-executed at reboot. BlockBlock continually monitors common persistence locations and displays an alert whenever a persistent component is added to the OS.

\n

[…]

\n

This alert contains the name and path of the process that installed the persistent component, as well as details about the actual persistent component. Moreover, it shows if the process (that created the persisted item) is signed by Apple, signed by a 3rd-party, or is unsigned[…]

\n
\n

It’s the equivalent of LittleSnitch for auto-launching background processes.

\n

Previously:

\n","direction":"ltr"},"title":"BlockBlock 0.9.9.4","author":"Michael Tsai","summary":{"content":"Patrick Wardle (via Leo M): Malware installs itself persistently, to ensure it’s automatically re-executed at reboot. BlockBlock continually monitors common persistence locations and displays an alert whenever a persistent component is added to the OS. […] This alert contains the name and path of the process that installed the persistent component, as well as details […]","direction":"ltr"},"alternate":[{"href":"https://mjtsai.com/blog/2019/09/27/blockblock-0-9-9-4/","type":"text/html"}],"crawled":1569609764000,"published":1569609764000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","htmlUrl":"https://mjtsai.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d7407eee0:205a:90d684ff","keywords":["Technology","Apple T2","Mac","macOS 10.15 Catalina","Secure Boot","Terminal"],"originId":"https://mjtsai.com/blog/?p=26726","fingerprint":"df4fbe8f","content":{"content":"

Howard Oakley:

\n

Even with Recovery Mode available, there have still been some reasons for wanting to enter SUM. One of them has been to run command tools to check memory for faults, as in SUM the system takes as little memory as possible, allowing you to run checks on all the rest. Sometimes fsck run from SUM could fix problems which Disk Utility in Recovery Mode couldn’t.

[…]

The biggest problem comes with Macs equipped with a T2 chip, and its Secure Boot. If it were available, a traditional SUM would bypass Secure Boot, so it isn’t allowed if your Mac has a T2 chip: Command-R will take you to Recovery, and that’s as close as your Mac can get, unless you disable Secure Boot by setting the Startup Security Utility to No Security. And you have to do that in Recovery anyway.

","direction":"ltr"},"title":"Is Single-user Mode Dead?","author":"Michael Tsai","summary":{"content":"Howard Oakley: Even with Recovery Mode available, there have still been some reasons for wanting to enter SUM. One of them has been to run command tools to check memory for faults, as in SUM the system takes as little memory as possible, allowing you to run checks on all the rest. Sometimes fsck run […]","direction":"ltr"},"alternate":[{"href":"https://mjtsai.com/blog/2019/09/27/is-single-user-mode-dead/","type":"text/html"}],"crawled":1569609740000,"published":1569609740000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","htmlUrl":"https://mjtsai.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d7407ac78:2059:90d684ff","keywords":["Technology","App Review","App Store Rejection","App Subscriptions","Business","iOS","iOS 13","iOS App"],"originId":"https://mjtsai.com/blog/?p=26724","fingerprint":"3e02b0d2","content":{"content":"

David Barnard (tweet):

\n

Paying once for an app really only makes sense if the app provides minimal functionality of limited value and won’t benefit from continued improvement.

\n

[…]

\n

With paid apps, people often end up buying several apps just to figure out which one best fits their needs. Let’s say they spent $3 each on 4 apps, that’s $12 they were willing to pay for a great app, but that great app they settled on only gets $3. Since most subscription apps have free trials, and many even have ad supported free tiers, people can try multiple apps and then only pay for the one they actually use.

\n

[…]

\n

Sure, some potential customers (or existing customers if you transition from another business model to subscriptions) are going to complain about the subscription model. It’s hard to hear someone tell you that they don’t value what you’ve built, but the smart thing to do is focus on the people who are subscribing, your true fans. Figure out who they are and look for ways to reach more people like them instead of focusing on the vocal minority that complain.

\n

Julian Schiavo:

\n
\n

My apps been rejected with a short message basically saying ‘your app can’t use auto renewing subscriptions’, any tips/experience on this? All the other apps of this type use auto renewing subscriptions \uD83E\uDD37‍♂️

\n
\n

Reginald Braithwaite:

\n
\n

The simple model I keep in my head, is that your revenue model should parallel your expense model.

\n

If they don’t, you’re both running a business AND engaging in arbitrage. That’s two things you have to get right, instead of one.

\n
\n

David Barnard:

\n
\n

This tweet reminds me of a chart by @macguru17. When you’re building an app, even one that doesn’t have ongoing costs like weather data or servers, the cost of continued development keeps putting you underwater before each update.

\n

I don’t think most people realize how much most indie developers sacrifice to keep the lights on. In 11 years, I’ve probably only been cash flow positive 48 months. With paid apps, I’d build up a war chest with a big launch/update/sale, then spend it down working on the next.

\n

I’ve even gone into debt to squeak by until the next big update. People seem to think all developers are rich & greedy. But it’s like any other small business. Most struggle, some do well, a few really well. But businesses don’t drop prices because they are doing well. \uD83D\uDE43

\n
\n

Previously:

\n","direction":"ltr"},"title":"Subscribers Are Your True Fans","author":"Michael Tsai","summary":{"content":"David Barnard (tweet): Paying once for an app really only makes sense if the app provides minimal functionality of limited value and won’t benefit from continued improvement. […] With paid apps, people often end up buying several apps just to figure out which one best fits their needs. Let’s say they spent $3 each on […]","direction":"ltr"},"alternate":[{"href":"https://mjtsai.com/blog/2019/09/27/subscribers-are-your-true-fans/","type":"text/html"}],"crawled":1569609723000,"published":1569609723000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","htmlUrl":"https://mjtsai.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]},{"id":"ov+64/RrrpHw8vHNs9Rbgmq2LwWdAk5I0cvaa8ohxPI=_16d74074eb8:2058:90d684ff","keywords":["Technology","iOS","iOS 12","iOS App","Twitter","Web"],"originId":"https://mjtsai.com/blog/?p=26722","fingerprint":"79c4af3b","content":{"content":"

Tim Hardwick:

\n

Twitter today rolled out its new “Hide Replies” feature in the U.S. and Japan, providing Twitter users with more control over the replies that are visible following a tweet.

The idea behind the feature is to give people more control over the conversations they start on the social media platform, so they can hide replies that are offensive and the hidden reply won’t show up to others as a response to the original tweet.

\n

Khedron:

\n
\n

So people can post false information and hide/censor anyone who calls them out on their lies or conduct.

\n
\n

Ben Sandofsky:

\n
\n

Decided to try the “Hide Reply” feature, and I noticed the hidden tweets got way more attention than if I’d just ignored them.

\n

It turns out this pop up appears when you visit my tweet.

\n
\n

Juli Clover:

\n
\n

Twitter today implemented a feature that’s designed to allow lists of Twitter users to be pinned to the Home screen and swiped between, allowing for easy access to multiple customizable timelines.

\n
","direction":"ltr"},"title":"Twitter “Hide Replies” and Timeline Swiping","author":"Michael Tsai","summary":{"content":"Tim Hardwick: Twitter today rolled out its new “Hide Replies” feature in the U.S. and Japan, providing Twitter users with more control over the replies that are visible following a tweet.The idea behind the feature is to give people more control over the conversations they start on the social media platform, so they can hide […]","direction":"ltr"},"alternate":[{"href":"https://mjtsai.com/blog/2019/09/27/twitter-hide-replies-and-timeline-swiping/","type":"text/html"}],"crawled":1569609699000,"published":1569609699000,"origin":{"streamId":"feed/http://mjtsai.com/blog/feed/","title":"Michael Tsai","htmlUrl":"https://mjtsai.com/blog"},"unread":true,"categories":[{"id":"user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366","label":"Weblogs"}]}]} \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/TestAccountManager.swift b/Frameworks/Account/AccountTests/TestAccountManager.swift index 06627e4d7..6c75126f8 100644 --- a/Frameworks/Account/AccountTests/TestAccountManager.swift +++ b/Frameworks/Account/AccountTests/TestAccountManager.swift @@ -23,16 +23,16 @@ class TestAccountManager { func createAccount(type: AccountType, username: String? = nil, password: String? = nil, transport: Transport) -> Account { let accountID = UUID().uuidString - let accountFolder = accountsFolder.appendingPathComponent("\(type.rawValue)_\(accountID)").absoluteString + let accountFolder = accountsFolder.appendingPathComponent("\(type.rawValue)_\(accountID)") do { - try FileManager.default.createDirectory(atPath: accountFolder, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.createDirectory(at: accountFolder, withIntermediateDirectories: true, attributes: nil) } catch { assertionFailure("Could not create folder for \(accountID) account.") abort() } - let account = Account(dataFolder: accountFolder, type: type, accountID: accountID, transport: transport)! + let account = Account(dataFolder: accountFolder.absoluteString, type: type, accountID: accountID, transport: transport)! return account @@ -43,8 +43,11 @@ class TestAccountManager { do { try FileManager.default.removeItem(atPath: account.dataFolder) } + catch let error as CocoaError where error.code == .fileNoSuchFile { + print("Unable to delete folder at: \(account.dataFolder) because \(error)") + } catch { - assertionFailure("Could not create folder for OnMyMac account.") + assertionFailure("Could not delete folder at: \(account.dataFolder) because \(error)") abort() } diff --git a/Frameworks/Account/DataExtensions.swift b/Frameworks/Account/DataExtensions.swift index 95e144d20..a503ea62d 100644 --- a/Frameworks/Account/DataExtensions.swift +++ b/Frameworks/Account/DataExtensions.swift @@ -49,7 +49,11 @@ extension Feed { public extension Article { var account: Account? { - return AccountManager.shared.existingAccount(with: accountID) + // The force unwrapped shared instance was crashing Account.framework unit tests. + guard let manager = AccountManager.shared else { + return nil + } + return manager.existingAccount(with: accountID) } var feed: Feed? { diff --git a/Frameworks/Account/Feedly/FeedlyAPICaller.swift b/Frameworks/Account/Feedly/FeedlyAPICaller.swift index 68ad350b3..c30bd5dfb 100644 --- a/Frameworks/Account/Feedly/FeedlyAPICaller.swift +++ b/Frameworks/Account/Feedly/FeedlyAPICaller.swift @@ -97,9 +97,11 @@ final class FeedlyAPICaller { } var components = baseUrlComponents components.path = "/v3/streams/contents" + // If you change these, check AccountFeedlySyncTest.set(testFiles:with:). components.queryItems = [ + URLQueryItem(name: "unreadOnly", value: unreadOnly ? "true" : "false"), + URLQueryItem(name: "count", value: "500"), URLQueryItem(name: "streamId", value: collection.id), - URLQueryItem(name: "unreadOnly", value: unreadOnly ? "true" : "false") ] guard let url = components.url else { diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index cb6930069..996bdf287 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -31,7 +31,7 @@ final class FeedlyAccountDelegate: AccountDelegate { didSet { // https://developer.feedly.com/v3/developer/ if let devToken = ProcessInfo.processInfo.environment["FEEDLY_DEV_ACCESS_TOKEN"], !devToken.isEmpty { - caller.credentials = Credentials(type: .oauthAccessToken, username: "", secret: devToken) + caller.credentials = Credentials(type: .oauthAccessToken, username: "Developer", secret: devToken) } else { caller.credentials = credentials } @@ -88,6 +88,7 @@ final class FeedlyAccountDelegate: AccountDelegate { os_log(.debug, log: log, "Sync took %.3f seconds", -date.timeIntervalSinceNow) DispatchQueue.main.async { progress.completeTask() + completion(result) } } } @@ -241,11 +242,6 @@ final class FeedlyAccountDelegate: AccountDelegate { caller: caller, articleStatusCoordinator: articleStatusCoodinator, log: log) - - //TODO: Figure out how other accounts get refreshed automatically. - refreshAll(for: account) { result in - print("sync after initialise did complete") - } } static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { diff --git a/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift b/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift index d776e006d..f56e9372e 100644 --- a/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift +++ b/Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift @@ -43,25 +43,29 @@ final class FeedlyArticleStatusCoordinator { /// Ensures local articles have the same status as they do remotely. func refreshArticleStatus(for account: Account, stream: FeedlyStream, collection: FeedlyCollection, completion: @escaping (() -> Void)) { - guard let folder = account.existingFolder(with: collection.label) else { - completion() - return - } - let unreadArticleIds = Set( stream.items .filter { $0.unread } .map { $0.id } ) - let localArticles = folder.fetchArticles() - let localArticleIds = localArticles.articleIDs() - let readArticleIds = localArticleIds.subtracting(unreadArticleIds) - account.update(localArticles.filter { readArticleIds.contains($0.articleID) }, statusKey: .read, flag: true) -// account.ensureStatuses(readArticleIds, true, .read, true) - account.update(localArticles.filter { unreadArticleIds.contains($0.articleID) }, statusKey: .read, flag: false) -// account.ensureStatuses(unreadArticleIds, false, .read, false) - os_log(.debug, log: log, "Ensured %i UNREAD and %i read article(s) in \"%@\".", unreadArticleIds.count, readArticleIds.count, collection.label) + // Mark articles as unread + let currentUnreadArticleIDs = account.fetchUnreadArticleIDs() + let deltaUnreadArticleIDs = unreadArticleIds.subtracting(currentUnreadArticleIDs) + let markUnreadArticles = account.fetchArticles(.articleIDs(deltaUnreadArticleIDs)) + account.update(markUnreadArticles, statusKey: .read, flag: false) + + let readAritcleIds = Set( + stream.items + .filter { !$0.unread } + .map { $0.id } + ) + + let deltaReadArticleIDs = currentUnreadArticleIDs.intersection(readAritcleIds) + let markReadArticles = account.fetchArticles(.articleIDs(deltaReadArticleIDs)) + account.update(markReadArticles, statusKey: .read, flag: true) + + os_log(.debug, log: log, "\"%@\" - updated %i UNREAD and %i read article(s).", collection.label, unreadArticleIds.count, markReadArticles.count) completion() diff --git a/Frameworks/Account/Feedly/Models/FeedlyEntry.swift b/Frameworks/Account/Feedly/Models/FeedlyEntry.swift index a84cd9b35..8fefbb258 100644 --- a/Frameworks/Account/Feedly/Models/FeedlyEntry.swift +++ b/Frameworks/Account/Feedly/Models/FeedlyEntry.swift @@ -48,9 +48,13 @@ struct FeedlyEntry: Decodable { /// the feed from which this article was crawled. If present, “streamId” will contain the feed id, “title” will contain the feed title, and “htmlUrl” will contain the feed’s website. var origin: FeedlyOrigin? -// -// /// a list of alternate links for this article. Each link object contains a media type and a URL. Typically, a single object is present, with a link to the original web page. -// var alternate: [Link]? + + /// Used to help find the URL to visit an article on a web site. + /// See https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ + var canonical: [FeedlyLink]? + + /// a list of alternate links for this article. Each link object contains a media type and a URL. Typically, a single object is present, with a link to the original web page. + var alternate: [FeedlyLink]? // // // var origin: // // Optional origin object the feed from which this article was crawled. If present, “streamId” will contain the feed id, “title” will contain the feed title, and “htmlUrl” will contain the feed’s website. @@ -62,8 +66,8 @@ struct FeedlyEntry: Decodable { /// Was this entry read by the user? If an Authorization header is not provided, this will always return false. If an Authorization header is provided, it will reflect if the user has read this entry or not. var unread: Bool // -// /// a list of tag objects (“id” and “label”) that the user added to this entry. This value is only returned if an Authorization header is provided, and at least one tag has been added. If the entry has been explicitly marked as read (not the feed itself), the “global.read” tag will be present. -// var tags: [Tag]? + /// a list of tag objects (“id” and “label”) that the user added to this entry. This value is only returned if an Authorization header is provided, and at least one tag has been added. If the entry has been explicitly marked as read (not the feed itself), the “global.read” tag will be present. + var tags: [FeedlyTag]? // /// a list of category objects (“id” and “label”) that the user associated with the feed of this entry. This value is only returned if an Authorization header is provided. var categories: [FeedlyCategory]? @@ -74,8 +78,8 @@ struct FeedlyEntry: Decodable { // /// Timestamp for tagged articles, contains the timestamp when the article was tagged by the user. This will only be returned when the entry is returned through the streams API. // var actionTimestamp: Date? // -// /// A list of media links (videos, images, sound etc) provided by the feed. Some entries do not have a summary or content, only a collection of media links. -// var enclosure: [Link]? + /// A list of media links (videos, images, sound etc) provided by the feed. Some entries do not have a summary or content, only a collection of media links. + var enclosure: [FeedlyLink]? // // /// The article fingerprint. This value might change if the article is updated. // var fingerprint: String diff --git a/Frameworks/Account/Feedly/Models/FeedlyEntryParser.swift b/Frameworks/Account/Feedly/Models/FeedlyEntryParser.swift new file mode 100644 index 000000000..663573746 --- /dev/null +++ b/Frameworks/Account/Feedly/Models/FeedlyEntryParser.swift @@ -0,0 +1,103 @@ +// +// FeedlyEntryParser.swift +// Account +// +// Created by Kiel Gillard on 3/10/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import Articles +import RSParser + +struct FeedlyEntryParser { + var entry: FeedlyEntry + + var id: String { + return entry.id + } + + var feedUrl: String { + guard let id = entry.origin?.streamId else { + assertionFailure() + return "" + } + return id + } + + /// Convoluted external URL logic "documented" here: + /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ + var externalUrl: String? { + let multidimensionalArrayOfLinks = [entry.canonical, entry.alternate] + let withExistingValues = multidimensionalArrayOfLinks.compactMap { $0 } + let flattened = withExistingValues.flatMap { $0 } + let webPageLinks = flattened.filter { $0.type == nil || $0.type == "text/html" } + return webPageLinks.first?.href + } + + var title: String? { + return entry.title + } + + var contentHMTL: String? { + return entry.content?.content ?? entry.summary?.content + } + + var contentText: String? { + // We could strip HTML from contentHTML? + return nil + } + + var summary: String? { + return entry.summary?.content + } + + var datePublished: Date { + return entry.published + } + + var dateModified: Date? { + return entry.updated + } + + var authors: Set? { + guard let name = entry.author else { + return nil + } + return Set([ParsedAuthor(name: name, url: nil, avatarURL: nil, emailAddress: nil)]) + } + + var tags: Set? { + guard let labels = entry.tags?.compactMap({ $0.label }), !labels.isEmpty else { + return nil + } + return Set(labels) + } + + var attachments: Set? { + guard let enclosure = entry.enclosure, !enclosure.isEmpty else { + return nil + } + let attachments = enclosure.compactMap { ParsedAttachment(url: $0.href, mimeType: $0.type, title: nil, sizeInBytes: nil, durationInSeconds: nil) } + return attachments.isEmpty ? nil : Set(attachments) + } + + var parsedItemRepresentation: ParsedItem { + return ParsedItem(syncServiceID: id, + uniqueID: id, + feedURL: feedUrl, + url: nil, + externalURL: externalUrl, + title: title, + contentHTML: contentHMTL, + contentText: contentText, + summary: summary, + imageURL: nil, + bannerImageURL: nil, + datePublished: datePublished, + dateModified: dateModified, + authors: authors, + tags: tags, + attachments: attachments) + } +} diff --git a/Frameworks/Account/Feedly/Models/FeedlyFeed.swift b/Frameworks/Account/Feedly/Models/FeedlyFeed.swift index da470b78f..43d60f7e9 100644 --- a/Frameworks/Account/Feedly/Models/FeedlyFeed.swift +++ b/Frameworks/Account/Feedly/Models/FeedlyFeed.swift @@ -13,4 +13,5 @@ struct FeedlyFeed: Codable { var id: String var title: String var updated: Date? + var website: String? } diff --git a/Frameworks/Account/Feedly/Models/FeedlyLink.swift b/Frameworks/Account/Feedly/Models/FeedlyLink.swift new file mode 100644 index 000000000..3756e1034 --- /dev/null +++ b/Frameworks/Account/Feedly/Models/FeedlyLink.swift @@ -0,0 +1,18 @@ +// +// FeedlyLink.swift +// Account +// +// Created by Kiel Gillard on 3/10/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct FeedlyLink: Decodable { + var href: String + + /// The mime type of the resource located by `href`. + /// When `nil`, it's probably a web page? + /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ + var type: String? +} diff --git a/Frameworks/Account/Feedly/Models/FeedlyResourceId.swift b/Frameworks/Account/Feedly/Models/FeedlyResourceId.swift new file mode 100644 index 000000000..3095f2709 --- /dev/null +++ b/Frameworks/Account/Feedly/Models/FeedlyResourceId.swift @@ -0,0 +1,37 @@ +// +// FeedlyResourceId.swift +// Account +// +// Created by Kiel Gillard on 3/10/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +/// The kinds of Resource Ids is documented here: https://developer.feedly.com/cloud/ +protocol FeedlyResourceId { + + /// The resource Id from Feedly. + var id: String { get } + + /// The location of the kind of resource a concrete type represents. + /// If the conrete type cannot strip the resource type from the Id, it should just return the Id + /// since the Id is a legitimate URL. + var url: String { get } +} + +/// The Feed Resource is documented here: https://developer.feedly.com/cloud/ +struct FeedlyFeedResourceId: FeedlyResourceId { + var id: String + + var url: String { + if let range = id.range(of: "feed/"), range.lowerBound == id.startIndex { + var mutant = id + mutant.removeSubrange(range) + return mutant + } + + // It seems values like "something/https://my.blog/posts.xml" is a legit URL. + return id + } +} diff --git a/Frameworks/Account/Feedly/Models/FeedlyTag.swift b/Frameworks/Account/Feedly/Models/FeedlyTag.swift new file mode 100644 index 000000000..853068478 --- /dev/null +++ b/Frameworks/Account/Feedly/Models/FeedlyTag.swift @@ -0,0 +1,14 @@ +// +// FeedlyTag.swift +// Account +// +// Created by Kiel Gillard on 3/10/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct FeedlyTag: Decodable { + var id: String + var label: String? +} diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift index 46c420c08..9b0df05bc 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift @@ -31,6 +31,19 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { let feedsBefore = localFeeds let pairs = collectionsAndFoldersProvider.collectionsAndFolders + // Remove feeds in a folder which are not in the corresponding collection. + for (collection, folder) in pairs { + let feedsInFolder = folder.topLevelFeeds + let feedsInCollection = Set(collection.feeds.map { $0.id }) + let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.feedID) } + if !feedsToRemove.isEmpty { + folder.removeFeeds(feedsToRemove) + os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection) + } + + } + + // Pair each Feed with its Folder. let feedsAndFolders = pairs .compactMap { ($0.0.feeds, $0.1) } .map({ (collectionFeeds, folder) -> [(FeedlyFeed, Folder)] in @@ -43,18 +56,15 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { // find an existing feed for feed in localFeeds { - if feed.feedID == collectionFeed.feedId { + if feed.feedID == collectionFeed.id { return (feed, folder) } } // no exsiting feed, create a new one - let url = collectionFeed.id - let metadata = FeedMetadata(feedID: url) - // TODO: More metadata - - let feed = Feed(account: account, url: url, metadata: metadata) - feed.name = collectionFeed.title + let id = collectionFeed.id + let url = FeedlyFeedResourceId(id: id).url + let feed = account.createFeed(with: collectionFeed.title, url: url, feedID: id, homePageURL: collectionFeed.website) // So the same feed isn't created more than once. localFeeds.insert(feed) @@ -69,6 +79,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { } } + // Remove feeds without folders/collections. let feedsAfter = Set(feedsAndFolders.map { $0.0 }) let feedsWithoutCollections = feedsBefore.subtracting(feedsAfter) for unmatched in feedsWithoutCollections { diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamParsedItemsOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamParsedItemsOperation.swift index 9e05b114a..9069a0637 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamParsedItemsOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyGetStreamParsedItemsOperation.swift @@ -45,32 +45,7 @@ final class FeedlyGetStreamParsedItemsOperation: FeedlyOperation, FeedlyStreamPa guard !isCancelled else { return } - parsedItems = stream.items.compactMap { entry -> ParsedItem? in - guard let origin = entry.origin else { - // Assertion might be too heavy handed here as our understanding of the data quality from Feedly grows. - print("Entry has no origin and no way for us to figure out which feed it should belong to: \(entry)") - return nil - } - - // TODO: Sensible values here. - let parsed = ParsedItem(syncServiceID: entry.id, - uniqueID: entry.id, - feedURL: origin.streamId, - url: nil, - externalURL: origin.htmlUrl, - title: entry.title, - contentHTML: entry.content?.content, - contentText: nil, // Seems there is no corresponding field in the JSON, so we might have to derive a value. - summary: nil, - imageURL: nil, - bannerImageURL: nil, - datePublished: entry.published, - dateModified: entry.updated, - authors: nil, - tags: nil, - attachments: nil) - return parsed - } + parsedItems = stream.items.map { FeedlyEntryParser(entry: $0).parsedItemRepresentation } os_log(.debug, log: log, "Parsed %i items of %i entries for %@", parsedItems.count, stream.items.count, collection.label) } diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index 9cfd52e18..d5d68077e 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -102,6 +102,14 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun topLevelFeeds.remove(feed) postChildrenDidChangeNotification() } + + public func removeFeeds(_ feeds: Set) { + guard !feeds.isEmpty else { + return + } + topLevelFeeds.subtract(feeds) + postChildrenDidChangeNotification() + } // MARK: - Hashable diff --git a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift index 18eb6d262..f24d44fd6 100644 --- a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift @@ -84,12 +84,15 @@ class AccountsFeedlyWebWindowController: NSWindowController, WKNavigationDelegat // TODO: Find an already existing account for this username? let account = AccountManager.shared.createAccount(type: .feedly) do { - try account.storeCredentials(grant.accessToken) + // Store the refresh token first because it sends this token to the account delegate. if let token = grant.refreshToken { try account.storeCredentials(token) } + // Now store the access token because we want the account delegate to use it. + try account.storeCredentials(grant.accessToken) + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { NSApplication.shared.presentError(error) From e1b0e17b6a1e1bd96992a0ce846bdb79d1edfdaf Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Thu, 3 Oct 2019 18:45:16 +1000 Subject: [PATCH 2/2] Allows folders to efficient add and remove many feeds. --- Frameworks/Account/Account.swift | 9 +++++++++ .../FeedlyCreateFeedsForCollectionFoldersOperation.swift | 4 +--- Frameworks/Account/Folder.swift | 8 ++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 6619a627a..c34f3c19a 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -679,6 +679,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, postChildrenDidChangeNotification() } + public func removeFeeds(_ feeds: Set) { + guard !feeds.isEmpty else { + return + } + topLevelFeeds.subtract(feeds) + structureDidChange() + postChildrenDidChangeNotification() + } + public func addFeed(_ feed: Feed) { topLevelFeeds.insert(feed) structureDidChange() diff --git a/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift b/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift index 9b0df05bc..b44e249fe 100644 --- a/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift +++ b/Frameworks/Account/Feedly/Refresh/FeedlyCreateFeedsForCollectionFoldersOperation.swift @@ -82,9 +82,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { // Remove feeds without folders/collections. let feedsAfter = Set(feedsAndFolders.map { $0.0 }) let feedsWithoutCollections = feedsBefore.subtracting(feedsAfter) - for unmatched in feedsWithoutCollections { - account.removeFeed(unmatched) - } + account.removeFeeds(feedsWithoutCollections) if !feedsWithoutCollections.isEmpty { os_log(.debug, log: log, "Removed %i feeds", feedsWithoutCollections.count) diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index d5d68077e..77261889a 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -98,6 +98,14 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun postChildrenDidChangeNotification() } + public func addFeeds(_ feeds: Set) { + guard !feeds.isEmpty else { + return + } + topLevelFeeds.formUnion(feeds) + postChildrenDidChangeNotification() + } + public func removeFeed(_ feed: Feed) { topLevelFeeds.remove(feed) postChildrenDidChangeNotification()