The work and times of the app development team at Steamclock.

Archive (28) RSS Feed

Allen Pike • February 21, 2019

Navigation Should Be Boring

In what could have been a Steamclock blog post, I recently wrote an article on why for new apps, navigation should be boring:

With a delightfully boring navigation scheme, users don’t need to learn how to explore your app. Their “attention span budget” can thus be spent considering how your new thing can fit into their lives, rather than trying to recall how many fingers they’re supposed to drag from the left side of the screen in order to pull out the Alternate Quick Access Wheel.

As fun as designing novel navigation schemes may be, there a lot of better ways to make a new app distinctive and appealing.

Accent Accent
2 21 19 112
Allen Pike • January 7, 2019

An App Called Quests

Today we’re launching a new Mac app called Quests for tracking issues and pull requests in your menu bar. Here’s how it came to be.

Quests Screenshot

At Steamclock, we work on a variety of projects at once. Between client projects, open source libraries, and internal labs projects – across iOS, Android, Mac, and the web – we have quite a few repositories and issue trackers going around.

We’re also fans of code review. That means team members sometimes review pull requests for projects that they’re not otherwise working on. Code review is a great way to improve quality and reduce the “bus factor” of projects, but it definitely creates more pull requests and issue traffic.

As a result, our pull request and issue notification emails can get rather noisy. Each of us works across multiple repositories, and often even multiple issue trackers or source control systems. At a certain volume, issues or pull requests can get lost in the shuffle, which can really sap a team’s velocity.

So, we thought it’d be nice if you could easily see the pull requests and issues assigned to you. Maybe right up there in your Mac’s menu bar. So we built that, and turns out: it is nice. We called this little app Quests.

Quests Logo

At Steamclock we love to iterate, refine, polish, and add to a product until it’s beautiful and exceptional. With Quests, we challenged ourselves instead to ship it, and then polish it to its full potential beauty.

So we have plans for how we’d like Quests to look, and future subscription features that would let us add additional source control and issue tracking systems beyond GitHub.com and GitLab.com. But today, Quests is useful. Almost everybody at Steamclock uses it already.

So today, Quests is available for free on the Mac App Store. If you think it could be useful to you, try it out. If it is useful to you, let us know!

Accent Accent

Try Quests today.

Download on the Mac App Store
Allen Pike • December 17, 2018

A New Look for Steamclock

Last week, we launched a totally redesigned Steamclock website and branding. We think it’s neat, so here’s a bit about it.

The Braaand

It’s easy to go overboard on value statements and brand visions. “Our mission is to optimize synergy for shareholders,” sure great – very inspiring. When it comes to doing design and branding work though, it is really helpful to lay out what the message is. What is Steamclock about?

In their briefest form, here are the 3 adjectives we built the Steamclock brand around, and the three “guardrail” terms we wanted to stay cautious of:

  • High quality, but not corporate.
  • Playful, but not wacky.
  • Nice, but not fancy.

With these in hand, it was a lot easier to discuss proposed designs, colours, fonts, and the like. If you’re in the position of being a founder that is involved in design work, it’s super important that your team has design goals that aren’t just “the founder likes it”.

Nicer Teeth

The previous Steamclock logo was designed by me, back in the early days. Protip: If your CEO designed your logo, it is highly likely that there is room for improvement.

Steamclock Logo Comparison

In particular, the new logo makes three big improvements:

  • Its details are much clearer at small sizes
  • A clock has 12 hours, so a gear clock should have 12 teeth, obviously
  • Its execution is nicer and has more character

We also modernized our font choices, and picked a colour palette that felt playful and distinctive – a far cry from the corporate blue that our previous branding had slowly descended into.

Steamclock Brand Guide

As it happened, we chose coral as one of our colours, which has since become an iPhone color and was just named Pantone Color of the Year 2019. We’ll need to keep an eye out that it doesn’t look “soo 2019” in a couple years, but for now we love it.

Tying the Room Together

Brand in hand, we filled the new site with bold colours, bold fonts, and nice illustrations. Illustration has become a big enough part of our work that it deserves its own post, but it’s proven an excellent way to communicate – without indulging my habit for excessive wordiness.

We’re rather proud of how it all turned out, and all the hard work that Erica and Brady did here to design and launch it. Of course, if you have any feedback or thoughts we’d love to hear them!

Accent Accent
12 17 18 427
Allen Pike • December 6, 2018

DJ Apps: Closing Time

Once upon a time, Steamclock launched two DJ apps for iOS: WeddingDJ and Party Monster. The pitch was simple: a queue-centric flow, smooth crossfades, and an easy interface for amateur DJs.

WeddingDJ was a hit with brides, grooms, and semi-pro DJs alike. Party Monster was well-loved for its opinionatedly queue-based UI, and fans enjoyed its habit of refusing to play Nickelback. Before long, these apps were paying our rent – which is a rare milestone in the indie iOS app world.

Then, the cloud happened.

Man Yells At Cloud

Don’t get me wrong, the cloud is great. I love the cloud, some of my best friends are clouds.

However, Apple’s transition from iTunes downloads to cloud-based streaming gradually hobbled this kind of DJ app. First it was iTunes in the Cloud, and iTunes Match – cool services, but they arrived without any way for apps like ours to trigger a download and make these tracks available to play.

Then came the bomb: Apple Music. Not only can crossfading DJ apps like ours not download Apple Music songs, we can’t even play downloaded tracks, since they they are protected with DRM.

As we wrote in 2015, this has been a pain for our users, and attracts angry and frustrated reviews. Even with app descriptions that started with all-caps “THIS APP DOES NOT WORK WITH APPLE MUSIC“, we’d get users who were upset – and reasonably so – because they’d downloaded our apps but couldn’t use them with their music.

So in 2016, we rounded up the various bug reports we’d made and published a review of the various issues and limitations with the APIs for DJ apps. Essentially, we needed either improvements to the the crossfade-capable but low-level AVFoundation APIs to let them play Apple Music, or additions and fixes to the high-level MPMediaPlayer APIs to support crossfading and queueing.

Livin’ On A Prayer

To their credit, Apple soon made a handful of fixes that changed Apple Music support at the MPMediaPlayer level, but 2 years later there are still multiple showstopper issues. In 2018, it is still not possible to make a nice queueing and crossfading DJ app that can play Apple Music tracks in the background, so this use case is clearly not a priority for them.

In the meantime, our apps’ ratings just have just trended lower and lower. For every delighted customer who’d bought their music, we’d get one or two who had no use for a DJ app that didn’t support streaming.

Even though the apps still make some revenue, we’ve reached a tipping point. Too many users are having a bad experiences, and it’s only getting worse. That’s not the kind of product we want to sell at Steamclock. We’ve tried various avenues – filing bugs, investigating Spotify, and prototyping simple “Baby Party Monster” demos that are possible with Apple Music, but none of these paths lead to what our customers are asking for.

So, the time has come to sing one last song for Party Monster and WeddingDJ. Today, we removed them from sale. The App Store should allow people who own these apps to download them indefinitely, until a future iOS update eventually breaks compatibility. It was sad, but it’s time.

Don’t Stop Believin’

These apps powered more than 100,000 weddings, along with years of parties and road trips. We loved developing them, and we’re so sorry we couldn’t find a path to modernizing them.

That said, we’ve learned a lot in the process, and have put those lessons into building dozens of other apps. We’ve shipped many for our clients, but in the last year we’ve been increasingly prototyping new internal apps, iterating them, and betting on ones worth shipping. This winter we’ll be shipping a Mac app for issues and pull requests, and next year we finally launch our turn-based spy game and v1.0 of our Bluejay iOS library.

Of course, that’s little consolation for folks who wish a nice app for crossfading and queueing Apple Music tracks could exist. We wish it could too. And hey, people are still trying! Maybe one day it will be possible.

We’ll keep an eye open – just in case.

Accent Accent
12 6 18 725
Rob MacEachern • November 20, 2018

On the Origin of isMultiple

How isMultiple became part of Swift, and what I learned along the way.

We’ve been using Swift at Steamclock for almost four years, and in that time it’s come a very long way. While Apple has of course done a momentous amount of work on the language in that time, a lot of Swift’s maturation can be attributed to the work of the community, by way of Swift Evolution.

Ideas move through Swift Evolution in three steps: a pitch, a proposal, and a review. There have been hundreds of reviewed proposals so far, ranging from the addition of the Random API to the removal of C-style for loops, and get as deep as making improvements to the floating point and integer protocols.

These proposals and many more are discussed on the Swift Evolution forums every day, which is where I learned about Chris Eidhof’s proposal to add toggle() to Bool. toggle adds a convenient way to to negate the value of a boolean.

This is particularly useful when a boolean is nested deep in an object graph, like so:

// Before .toggle()
someObject.someProperty.someBoolProperty = !someObject.someProperty.someBoolProperty

// After .toggle()
someObject.someProperty.someBoolProperty.toggle()

Chris’ proposal was easy to understand, the implementation was only a few lines of code, and the changes positively impacted the ergonomics of the language. This proposal showed that improvements don’t necessarily require intimate knowledge of the Swift compiler, or obscure data structures.

At the same time, it exposed some divisions in the Swift Evolution community. Some users argued that such trivial additions weren’t worthy of inclusion in the Swift Standard Library. Accepting toggle, in their eyes, would inspire a horde of trivial additions that would pollute the API surface of the standard library.

Despite these objections, the toggle() proposal was accepted on March 7, 2018, along with a refined set of proposal criteria and an explicit call for “further proposals to fill in gaps like this in the standard library.”

Which was great news, since toggle had planted a seed in my mind.

The Lightbulb

Around that time, I had been reading a book about Swift. Although its code samples were generally quite readable, there were a number of examples that included tests for even and odd numbers using the “remainder” operator, %.

.filter { integer in
    integer % 2 == 0
}

.skipWhile { integer in
    integer % 2 != 0
}

.takeWhile { index, integer in
    integer % 2 == 0 && index < 3
}

Many programmers will be familiar with integer % 2 == 0 as a check for a zero remainder after division by two – implying the number is even – or a non-zero remaninder indicating the number is odd. It’s not rocket science (although admittedly it may be a very tiny simple part of rocket science).

After seeing after seeing integer % 2 == 0 enough times, however, it started to look very strange to me. The strangeness may have been partly due to semantic satiation, but despite having used this pattern dozens of times in various projects – and despite it being standard practice for testing integers for “evenness” – it looked unnecessarily obtuse.

Meanwhile, I had recently been working with Ruby, which is full of developer niceties like odd? and even? . Naturally, I thought it’d be nice to be able to bring something similar to Swift:

.filter { integer in
    integer.isEven
}

.skipWhile { integer in
    integer.isOdd
}

.takeWhile { index, integer in
    integer.isEven && index < 3
}

A small nicety makes this kind of code a lot clearer and less error prone.

The Pitch

The next step was to write a pitch. A pitch is the first part of the Swift Evolution process: you can share a sketch of your proposed changes on the forum, and receive feedback from the community.

While writing a pitch for isEven/isOdd , I discovered someone had already started a pitch thread with the same idea, but progress on it had stalled. I considered starting a fresh forum thread, but decided to try reviving the original one with my own spin on the topic. I added my pitch based on the new post-toggle guidelines, and made some coffee.

Me: This shouldn’t be too controversial!

Narrator: It was.

One by one, responses started popping up. Swift core team members were chiming in. It was definitely exciting, but also surprisingly nerve-wracking. By the end of the pitch phase the thread had over 120 replies and over 5000 views, making it one of the top 10 most active pitch threads all-time on the forum. I don’t think anything I had ever written before had received so much feedback.

The responses were mostly favourable, but there were some concerns. The biggest was that isEven and isOdd didn’t address a big enough problem for inclusion in the standard library. Some folks didn’t seem to appreciate the value in moving away from the % operator – a move that helps avoid subtle bugs when working with negative values, especially due to differences between how % works in other programming languages.

The most common suggestion overall was to include a more general solution to the problem of integer divisibility. I was originally hesitant to expand the pitch because I couldn’t recall many times when I needed check for divisibility other than two (even/odd). I surveyed the source code of a variety of open source projects and found that 20-40% of divisibility testing was done for values other than two, so there was definitely a use case. The next thing you know, isEven/isOdd became the isEven/isOdd/isDivisible(by:) .

The Trouble With Zeros

The next issue at hand was getting correct handling of zeros with isDivisible. A typical mathy definition of divisibility usually looks something like:

If a and b are integers with a≠0, then we say a divides b if there exists an integer k such that b = ka.

Given the definition of divisibility, some argued that someNumber.isDivisible(by: 0) should be a precondition failure that crashes – but few developers would actually want that in practice.

On the other hand, relaxing the definition of divisibility to allow checks for divisibility by zero leads to a different strange situation: what if both values are zero?

let x = 0
let y = 0

// Zero is divisible by zero, according to the relaxed definition of divisibility.
if x.isDivisible(by: y) {
    // Crashes because division by zero traps in Swift. Divisibility != divideability
	x / y 
}

Well, that’s awkward. The code looks safe at first glance, but leads to an unceremonious – and likely unexpected – crash.

Thankfully, a community member proposed a clever workaround: rename isDivisible(by:) to isMultiple(of:). This provided the functionality we wanted, without implying anything about an actual literal mathematical division. This small tweak was so perfect and so simple, but it may not have ever come up without this broad public feedback process.

A month into the pitch phase, and it had evolved to be isEven/isOdd/isMultiple(of:) and all that was missing was implementation. Luckily for me, Apple’s Stephen Canon graciously provided an implementation complete with tests. Stephen is one of the most knowledgeable people on the planet when it comes to computational numerics, so getting an offer to use his implementation as part of the proposal was like an awesome, geeky gift from the programming gods.

Meanwhile, on another pitch thread, Swift core team member Ben Cohen posted a message expressing his concern that recent pitches contained “huge quantities of sugar and not enough protein”. This signalled that the isEven/isOdd/isMultiple(of:) review might be an uphill climb.

Review Begins

The first day of the review started on August 20, which coincided with the first day of my summer vacation. The review phase felt like a rollercoaster ride. A string of positive reviews would be posted and I’d be flying high, but then a scathing negative review would be posted which would leave me questioning why I’d even bothered proposing these changes in the first place. Well-known names in the Swift community like Erica Sadun and even Chris Lattner voted “-1”. My confidence sank, but I still stayed up each evening, responding to and addressing concerns that had been posted that day.

More than anything, the thing that surprised me was how passionate users were on each side of the proposal. This modest proposal, which started as a clear way to test if a number was odd.

Jordan Rose, another Swift core team member, pointed out that the proposal was a victim of Parkinson’s Law of Triviality:

Members of an organization will give disproportionate weight to trivial issues.

I suppose that’s what I get for making my first pitch something small. 😅

At the review’s conclusion a week later, supporters outnumbered opposers by roughly 2:1. It looked like isEven/isOdd/isMultiple had a chance of being accepted – but it was far from certain. I felt relieved that it was out of my hands, and I got back to enjoying my vacation. 🎣

A Decision Is Made

On August 30th, 7 weeks after my initial pitch post, the Swift core team announced new criteria for additions to the standard library:

To be considered for addition to the library, a proposed feature must satisfy two conditions: it must provide functionality that is useful to a substantial population of Swift programmers, and it must provide substantial advantages over the alternative ways of accomplishing that functionality.

Stern, but fair. Some examples of such advantages from the new guideline:

  • It may be complex, challenging, or error-prone for users to implement themselves.
  • It may have substantial performance advantages over a user implementation, either because it has access to library internals or just because the library implementation will likely be more carefully tuned.
  • It may be substantially easier to work with because it composes better with other language or library features.
  • It may be substantially more “fluent”: that is, more natural to discover, use, and read in code. The implementation may involve composing primitives in a subtle or tricky way, or the primitives may be unfamiliar to many programmers. This is a more subjective criterion than the others, and people may reasonably differ about how to apply it.

In the end, the core team felt that isMultiple did meet the “fluency” criteria. It could prevent bugs around negative remainders, and could have performance benefits for large integer types. The addition of isMultiple was accepted, but given the addition of isMultiple, isEven and isOdd did not cross the threshold for inclusion in the standard library.

And so the chapter closed, and with success! Well, partial success.

The decision rationale made sense. Still, a small part of me still felt like there was a certain je ne sais quoi about even and oddness that made isEven and isOdd worthy of inclusion too. But it was not to be.

Lessons, Odd and Even

I learned a lot at each phase of the process, and appreciate everybody who took time to help get this proposal into Swift.

For those considering a Swift proposal of their own, here are my key takeaways:

  1. It will take more effort than you expect. Writing the initial pitch is the easy part. Reading and responding to feedback, especially critical feedback, takes a lot of time and mental energy.
  2. Unexpected things can threaten to derail the entire discussion. My initial pitch included offhand comments about how value.isEven is fewer characters than value % 2 == 0 and that operator precedence rules impact the readability of statements that include the % operator. Both came back to haunt me repeatedly during discussions. Many users (rightfully) rejected the idea that API changes should be driven by character count and disputed my operator precedence claim. You should focus on the key selling points in your pitch, and cut everything else to avoid wasting time discussing weak side-points.
  3. You need an implementation. It wasn’t clear to me at the start of the process that an implementation is required in order to be reviewed. I was fortunate to receive a high quality implementation from Stephen Canon but you can’t assume someone will provide it for you. It’s not as scary as it sounds though! Building Swift from source was easier than I thought it’d be, and I don’t think it would have taken me too long to actually make the changes, if I’d needed to.
  4. Don’t take things too personally. Your pitches and proposals may be criticized harshly, but that’s part of the process. Be gracious when receiving feedback, and try to keep an open mind. The Swift forum is full of really smart people who want the same thing as you: a better programming language.

isMultiple(of:) will be part of Swift 5, which is scheduled for release sometime early in 2019. Despite it being more work than I expected, I’m glad to have been a part of the process. If you have an idea for an improvement to Swift – and can avoid running afoul of Parkinsons’ Law – then consider making a pitch of your own. ⚾️

Accent Accent
11 20 18 2349

The work and times of the app development team at Steamclock.

Archive (28) RSS Feed

Interested in future posts or announcements? Subscribe to our feed.