I’d done almost no iOS work since system 7 came out — but, once we were finished with OmniOutliner 5 for Mac (a release I’m super-proud of), it was time to go work on OmniOutliner 3 for iOS, and so they pulled me back in. :)
I was just one person on a much bigger team, of course. I helped indent some things and helped with filters. But there’s a whole bunch more besides — it’s a big release.
And it’s the first OmniOutliner for iOS release with a lower-cost Essentials version ($9.99). And it’s a free trial.
And I’m super-proud of this release, too.
The Scottish Play is mentioned, but only as “the Scottish play.” So we’re all good there — if you’re in a theater right now you can safely listen to this.
RSParser is now a CocoaPod! It’s my first.
I’m super-proud of myself for taking this the last few tiny steps of the way there — Silver Fox did all the rest of the much-appreciated work.
This means that if you’re a CocoaPods user, you can now go forth and parse feeds.
Still to do: support Carthage and Swift Package Manager. And, after this repo, there are at least a half-dozen more to do.
But that’s okay — doing open source is what I signed up for, and learning and using the infrastructure is part of the gig.
PS I really like what happens in the Terminal when you successfully publish a pod. Here’s a screenshot.
(Disclaimer: before I get started, I should take extra care to note that I don’t speak for Omni. This is my personal blog, with my personal opinions.)
Every time I make some criticism of the App Store — that, for instance, the 30% cut for Apple is too high, or that free trials would be a good thing — some number of people respond that Apple is a business and they’re allowed to do what they’re doing.
They may also remind me that this is capitalism, and that I can vote with my feet — that is, go create an Android app (or whatever) where the cost is presumably lower.
And they remind me that I should work with the world as it is, rather than the world as I want it to be.
They’re not wrong. Of course Apple is a business and is within their rights to charge whatever they want to charge, and developers could go do something else. And when making business decisions we have to look at facts and best extrapolations, not wishes and ponies.
But that misses the point entirely.
* * *
The point is that we are allowed — even in a capitalist system! — to criticize and to ask for changes. You can ask your spouse to put away the dishes more often; you can ask your kids to do their homework before dinner; you can ask the government for universal health care or corporate tax cuts; and you can ask Apple to lower the App Store cut.
Imagine if Starbucks charged $20 for a latte. You might complain about it and ask them to lower the price. Even if there’s another coffee shop nearby with much less expensive lattes, you still might.
Yes! Even in a capitalist system you can do this! It’s totally a-okay! Even if they’re within their rights (they are) to charge that much. Even if they are a business!
There’s no sacred verse that says businesses acting lawfully can’t be criticized. Nothing says we can’t advocate for change. In fact, I’d say that that’s part of capitalism, too.
* * *
So I got into a lengthy Twitter argument about Apple’s 30% App Store cut.
My thinking is that a lower cut provides more incentive for developers to invest in high-quality, long-lived apps — and that that’s good for the platform and good for users, and good for Apple, and so everybody wins.
It’s at least worth trying (this being capitalism, there are no guarantees of success) — and, since Apple is the wealthiest company in the history of companies, they could afford to try this.
If I’m right, then everybody wins: Apple, users, and developers. And if I’m wrong, Apple is not in any financial jeopardy.
* * *
I don’t think I’m misunderstanding or breaking the rules of capitalism by saying this. Nor am I telling developers to base their business decisions on fantasies.
But somebody will tell me that I am.
We could be excused for thinking that Micro.blog is like App.net — a Twitter alternative greeted with enthusiasm but that eventually closed.
It’s not the same thing, though, and I’ll explain why.
Micro.blog is not an alternative silo: instead, it’s what you build when you believe that the web itself is the great social network.
That’s the important part: even if Micro.blog doesn’t last (though I believe it will), the idea — that the web itself is where we are and where we talk to each other — will continue.
And: Micro.blog could be just one of thousands of similar services. And those services would all work together, because they’re made of web-stuff.
The Dream of 1999
Pick your year. I like 2003, since that was when I released NetNewsWire 1.0, an early Mac RSS reader. You might prefer 2000 (nice round number) or 2005 (things were a bit more advanced).
But if you think of the years 1995-2005, you remember when the web was our social network: blogs, comments on blogs, feed readers, and services such as Flickr, Technorati, and BlogBridge to glue things together. Those were great years — but then a few tragedies happened: Google Reader came out, and then, almost worse, it went away. Worse still was the rise of Twitter and Facebook, when we decided it would be okay to give up ownership and let just a couple companies own our communication.
Even if those companies had the public interest in mind — and they most definitely do not — they hold far too much power over something too fundamental to give up: our own human voices.
Twitter and Facebook are convenient, sure, but so are fossil fuels, and the cost was similarly unknown for a long time. But now we have some idea just how bad these things are for the world.
Micro.blog rewinds us to 2005 (or pick your year) — but, also, it has learned the lesson that people really like a timeline of short posts. People like being able to write and reply easily to other people. Good to know!
When you post to Micro.blog, you’re posting to an actual blog with an RSS feed and everything. The blog might be hosted by Micro.blog, or it might be some other blog somewhere else. (Could be a WordPress blog, for instance.)
Your posts are just a normal, everyday part of the open web. At this writing, mine appear on micro.inessential.com — but it’s on my to-do list to have those appear on my main blog (this blog) instead. (Probably won’t happen until after I ship the app I’m currently working on.)
And this is how it used to be, and how it never should have stopped being: my blog is me on the web. I own my blog: I own me.
And so everyone who follows me on Micro.blog sees my blog posts, and I see theirs. Simple.
And anyone who wants to could just read my blog in an RSS reader instead. All good, all open.
Replies are a little trickier. (Micro.blog is not a finished thing — the-web-as-social-network is not a finished thing, and, we hope, never will be. That’s totally fine.)
Replies don’t appear on your blog (though this could become an option, I suppose) — but they are sent as a WebMention when possible, which means even replies are part of the open web. You can read more about replies and @-mentions on the help site.
I expect this area to get more work in the future, especially as it’s part of the key to making Micro.blog part of the great social network but not the great social network (the web itself).
The app in your pocket
If you’re running the Micro.blog app on your iPhone or Mac, it really does look like a slimmed-down Twitter. This is by design. But don’t let that deceive you.
If the web is a river, Micro.blog is water, where Twitter and Facebook are dams.
What about my Uncle Joe?
You might think this is too difficult for normal people, that it’s all too nerdy, and that it won’t make headway against Twitter, so who cares.
My reply: it’s okay if this is a work in progress and isn’t ready for everybody yet. It’s okay if it takes time. We don’t know how it will all work in the end.
We’re discovering the future as we build it.
Check out episode #7 (I wish we had called it 007) where Ken Case talks about all the good stuff coming up in 2018.
You can subscribe to the podcast or just listen to this episode — there’s a player on the page. Or you can read the transcript.
* * *
I’ve been wondering if other companies in our world are doing similar podcasts.
It may be that Omni’s size is somewhat unique in the Mac and iOS world: it’s big enough that we won’t run out of people to interview and topics to talk about, which could be tricky for a 5- or 10-person company. And it’s small enough to still be intimate — my goal is to eventually interview every single person who works here. (Though, of course, people can decline: it’s not required!)
Anyway: there was a time before companies had blogs, and now they have blogs. I wonder if more and more companies will find value in having a podcast too.
When IndieWeb started, some years back, the first thing I noticed was that they appeared to be against the idea of feeds — or, at least, what they called side feeds: RSS and similar.
Their idea was that the data a reader might collect should be encoded in the page itself, using microformats. The argument (I think; I hope I’m not misrepresenting anyone) was that microformats are simpler than mantaining a separate file.
I don’t know which thought occurred to me first:
I took it personally, since I’d spent much of my career working with feeds, and I took this as a suggestion that my career was all wrong and they wouldn’t be interested in someone like me, someone who would be an ally in their committment to the open web.
I objected on practical grounds, too. Feeds have been and remain tremendously successful — see podcasting, for one thing — and the way forward is to build on that success. And, while I understand the elegance of microformats, side feeds have critical advantages that microformat-feeds don’t.
So I decided to ignore IndieWeb.
Here’s the thing, though: that was me being a total jerk.
(I’m telling you about this so you can learn from my mistake.)
But Manton also works with IndieWeb, and has mentioned any number of good ideas they have that I should look into.
It took me a while, but I realized that I was acting like a Clinton or Sanders supporter still arguing with the other side about the 2016 primaries — after losing the general election to a monster, after the point where those small differences matter at all.
And then I started to get excited about IndieWeb. I may have a difference of opinion when it comes to feeds, but who the hell cares when there’s so much good stuff to do? (They’re not holding my opinion against me; why should I hold theirs against them?)
And so I decided to support the h-feed format in Evergreen (hopefully in 1.0). That’s just for starters: I also decided to look into everything else and see what makes sense to support.
My default position now is: if a format or API or syncing service makes sense for a feed reader, then I’ll (at least try to) support it.
I published the first draft of Evergreen’s Coding Guidelines yesterday.
I posted this because I’d like to have people help me with the app — but I’m not ready yet. Or, rather, a few people who I know are starting to help, and I want to keep it small for now since I’ve never done this before.
If you’re interested in helping, I hope you’ll forgive me as I take it slow and learn how to manage an open source app with many contributors.
* * *
This isn’t the only app I want to do: there are several others. Ideally I’ll get to the point where I’m managing several open source apps — assuming people are interested in helping, which is a big assumption — and it’s all going great. But it will take time to get there.
(All my ideas are Mac and iOS apps that have something to do with the open web. I’ve always been happiest when working at that intersection.)
Anyway… it seemed like a good idea to write up coding guidelines early, so I did.
PS When I was faced with that blank document window, I started by typing the words “No subclasses,” because sheesh I really, really hate subclasses. The rest tumbled out.
There are, as I mentioned previously, two types of syncable actions: immediate and deferrable.
An immediate action is something like adding a feed: it requires that the server is reachable right now, so that the feed actually gets added.
A deferrable action is something like marking articles as read. The sooner the server knows, the better, sure — but it can wait if necessary. It can even wait for weeks or months.
I’ve decided to make it simple and categorize these actions like this: any structural action that affects the list of feeds and folders (or tags) is an immediate action, while any action that affects the status (read, starred) of articles is deferrable.
Or, in UI terms: if it’s something you do in the sidebar, it’s immediate; it it’s something you do in the timeline, it’s deferrable.
Most importantly: you can read articles while you’re offline.
Discarded alternate approach
You could argue that all actions should be deferrable. For instance, Evergreen could remember that you want to add a given feed, and add that add-feed action to the sync queue and wait for the server to become reachable again.
But doing so would add more chance for conflicts. Consider the case where the add-feed action got queued (on your day Mac) and then just sat there. Then, on your night Mac, you add the feed and it succeeds — then you decide you don’t like it after all, then delete it.
The next day, back on your day Mac, the add-feed call finally goes through, and then you have to delete it again. (And then you report an Evergreen syncing bug.)
There are other issues as well: do you add the feed locally? And read it? No, because this gets ambiguous quickly, as Evergreen’s article IDs won’t match the article IDs the server has assigned. Evergreen might not even have found the same feed URL the server found (in the case where you just gave it a website URL).
So we’ll stick with these two categories, immediate and deferrable.
To refine the definitions: a deferrable action is one where the state change can be reflected locally without risking serious conflicts or ambiguity.
These will result in an immediate API call, and the result will be reflected in the UI. This is straightforward.
These actions will be stored in a database.
The simplest way to do this is probably a set of tables: articlesToMarkRead, articlesToMarkUnread, articlesToStar, articlesToUnstar. Each table would have one single column:
This layout is probably the most efficient way to add/delete articles.
This is not actually an ordered queue, but I think that’s okay. Most of the time all four tables will be empty.
When articles are marked read (for instance), then those articles will be added to articlesToMarkRead and deleted from articlesToMarkUnread. The system will then know that it has pending status changes to the send to the server.
Then, periodically, the app will attempt to contact the server. Most of the time this should happen within a minute or so.
Part of the idea is using this system to coalesce calls: the app shouldn’t call the server every time you select an article (which marks it read). Better to update the database and call the server very-soon-ish, but not necessarily this exact second, to allow for multiple articles to queue up.
The downside to this particular layout, which may make me change it, is that supporting additional status types means adding more tables. But the alternative is to add more columns to a single table, which is better but not necessarily that much better.
Another downside is that there’s no automatic guarantee that an article ID won’t exist in, say, both articlesToMarkRead and articlesToMarkUnread at the same time. But the answer here is simple, careful coding: a single bottleneck function that never adds to one without deleting from the other.
The hard part is this: what happens when the server is not reachable for some reason?
Continuing the example: Evergreen could, in the case of the unreachable server, refuse to mark those articles as read.
It could insist that syncing works only when the server is reachable — which is not that weird, especially given that it can’t pull new data from the server unless it’s reachable.
But it’s weird enough. Consider that selecting an article marks it as read. This policy would make it impossible to even just read what you’ve already downloaded.
It’s clear that syncing requires some sort of offline ability.
Rather than set a policy for each possible user action (marking articles read, deleting a feed, etc.), I want to set a single policy and single structure for handling these actions.
(Note: there will be some things that do actually require a reachable server: downloading new articles and adding a feed come to mind. This design refers to actions where it would be okay to defer notifying the server.)
When the user performs a syncable and deferrable action, that action and the relevant parameters will be remembered.
Evergreen will attempt to notify the server periodically. Once it succeeds, it will forget that action and its parameters.
(Update 18 Jan: 2018: the original version of this design had a time limit, which has been removed. The concern was that this offline queue would grow unbounded — but it can’t, since if you can’t reach the server you can’t download new articles, which means the queue is limited to your current local data set.)
When the user performs a deferrable action, the app tries immediately to notify the server.
If this first attempt fails, then the action is recorded in a SQLite database with a timestamp and with the parameters it needs to be able to make that call again later. Those parameters will be minimal — article IDs instead of entire Article objects, for instance.
The app will periodically attempt to empty this database: starting with the oldest action, it will notify the server of each. On success, a given action is removed from the database.
Evergreen supports undo as much as possible. For instance, if you mark a number of articles as read, you can undo it.
In this case, Evergreen will notify the server of the mark-read action, and then, upon undo, notify the server of a mark-unread action, thereby undoing the effect of the first action.
In the case where the mark-read action is in the offline action queue, that will be found and deleted, and there will be no queued action to reverse the effect.
Actions that require an immediate response from a reachable server include actions such as adding a feed. In the case where a user attempts one of these and the server can’t be reached, an error sheet will explain the problem. Such actions will not be queued.
There will not, however, be an error sheet for performing a refresh when the server is unreachable. Instead, in that case, a warning icon is placed next to it in the sidebar, a la Mail and similar apps. Clicking the icon will attempt a connection to the server, and an error sheet explaining the problem will appear if the server still can’t be reached.
Evergreen works like Mail and similar apps in that it has a set of accounts for services such as Feedbin, Feedly, and others, along with an On My Mac account that is not synced.
Each account has its own section in the main window’s source list; each account has its own set of folders and feeds.
Accounts will be added to Evergreen in Preferences, in an Accounts pane.
The policies of these accounts will be respected. For example, if the On My Mac account automatically marks articles as read after n days, it’s possible that some service does this after some other number of days, or never does that at all, and Evergreen won’t override those policies for that service.
Another example: the On My Mac account doesn’t support folders-within-folders — but if a feed service does support that, then the app will support it too, for that service.
(Current plan: Evergreen 1.0 will ship with Feedbin support, and Evergreen 2.0 will add support for more services.)
Let me know if any of the above sounds weird or if I missed anything. (There is contact info at the bottom of Evergreen’s main page.)