{"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

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

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’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

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

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

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

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

\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

\nThe pieces I had to build to do this:\n

\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

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

\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
\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
\n    try:\n        create_table()\n    except Exception as e:\n        print("ERROR", e)\n

\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
\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
\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
\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

\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"}]}]}