Top notch UI copywriting from Wufoo.
About Ryan
Ryan's been getting to the bottom of things at Basecamp since 2003.
The thing [our clients] bring to the table is this extensive knowledge of their domain. The thing that we [software designers] bring to the table is the clarity that we think with. I don’t think our primary thing we bring to the table is our technical competence, although we need that. We need to be good enough to know how to do the implementation. But the thing that we bring that’s really critical to the process is we think sharply. We are able to abstract and we are able to define things crisply.
Another one from Eric Evans, but good stuff is good stuff. This time from Putting the model to work.
If you can’t understand it, you can’t change it.
Eric Evans on code and the reasons for thoughtful modeling. From his talk What I’ve learned about DDD since the book.
[Computing is] a place where you don’t have to be a Ph.D. or anything else. It’s a place where you can still be an artisan. People are willing to pay you if you’re any good at all, and you have plenty of time for screwing around.
Anil Dash on the real-time audience
When you are in an audience you see other people around you and there’s a social magic there. People band together, get excited, and things happen. Anil Dash thinks this reveals an opportunity on the increasingly “real time” web. How would sites or web apps be different if you could see who else is there with you in each moment?
Scope a variable to a block in your template code
Sometimes you are coding a template and you need to refer to the same method chain over and over. For example, you’re coding a template that summarizes activity on recent messages. You iterate through a block of messages, and for each message you want to display some information pertaining to the last comment. You could do it like this:
<div class="active_messages">
<% @active_messages.each do |message| %>
<h1><%= message.title %></h1>
<div class="latest_comment">
<div class="avatar">
<%= avatar_for(message.comments.last.creator) %>
</div>
Latest comment <%= time_ago_in_words(message.comments.last.created_at) %> ago by <%= message.comments.last.creator.full_name %>
</div>
<% end %>
</div>
Everything inside of div.latest_comment deals with the exact same comment, but we have to use a method chain to get the comment each time (message.comments.last).
One solution is to set a local variable inside the iterating block with the knowledge that the variable will be reset after each iteration:
<div class="active_messages">
<% @active_messages.each do |message| %>
<h1><%= message.title %></h1>
<% comment = message.comments.last %>
<div class="latest_comment">
<div class="avatar">
<%= avatar_for(comment.creator) %>
</div>
Latest comment <%= time_ago_in_words(comment.created_at) %> ago by <%= comment.creator.full_name %>
</div>
<% end %>
</div>
One on hand, this is better because the methods called on `comment` are easier to scan. The whole div.latest_comment is more readable without the repeated method chain. On the other hand, setting a local variable is bad style. The local variable assignment creates state without explicitly showing where that state applies. It feels a little too PHP for my taste.
A better approach is to use the `tap` method to scope a variable to a block:
<div class="active_messages">
<% @active_messages.each do |message| %>
<h1><%= message.title %></h1>
<div class="latest_comment">
<% message.comments.last.tap do |comment| %>
<div class="avatar">
<%= avatar_for(comment.creator) %>
</div>
Latest comment <%= time_ago_in_words(comment.created_at) %> ago by <%= comment.creator.full_name %>
<% end %>
</div>
<% end %>
</div>
The `tap` block shows exactly where the scope of the assignment starts and ends. I like how the template explicitly says “now we are going to deal with a comment in the following section, and this is the comment we are working with.”
I just hit on this pattern today while working on a feature and I think it’ll come in handy in the future.
Men don’t like appliances. We want things that can do lots of different things, that we can tweak and fiddle with, and then argue with each other about which one is better. Women aren’t like this, and because of this I have a feeling that it’s women who actually determine the eventual winners in consumer tech.
Ultimi Barbarorum on the iPad. Who knows if it’s true. But I can say this, whenever we hear praise from women on a product, it gives me more confidence that we hit the “useful” mark.
Perhaps my biggest interface pet peeve is alarm clocks in hotels. I stare at the controls for about ten minutes, give up, unplug it, and use my BlackBerry as my alarm clock. I have to unplug it because the last guy might have accidentally set it for 3 AM.
Micromanaging is something we only accuse other people of.
Hidden complexity in Highrise recordings
We’re exploring some very cool ideas to clarify the stream of notes and emails in Highrise. We want to make it easier to scan the streams and differentiate notes from emails from comments. Along the way we’re also cleaning up a lot of complex, overly-DRY code. It’s a great project, but it’s not quite as simple as it sounded at first.
Our initial plan sounded simple:
“Redesign the recording streams.”
“Recording” is an abstraction for the things that appear in streams: notes, comments, emails and more. They’re just notes that appear in a stream, like a blog index. How hard can it be?
When I started working on the new design and touching the code, I realized that recordings have a ton of dependencies. I made a quick and dirty graffle to keep them all in my head:
Recordings appear in multiple places: “aggregated streams” like cases and the dashboard, and “non-aggregated streams” like the stream for a particular person. Recordings include comments, and comments are mixed in with all other recordings but also appear in dedicated “comment streams” on recording perma pages. Recordings can be filed, they show privacy status, they have special states if you move them out of the current stream, and on and on. None of these things are obvious at first glance.
And none of this is a problem. We can even use this new design iteration to reduce the complexity. But it’s a good reminder that things often look simpler on the surface. When you dig into an established feature there may be a lot of dependencies and factors that only the source code and some careful spelunking will reveal.