Author: Jason Z.

  • Equality and remote teams

    One topic consistently comes up when people ask me how we do things at 37signals: working remotely. Talking with a friend about how his team manages a widely distributed team it occurred to me that the key to really making working outside the office effective for your team is equality.

    What I mean is that at 37signals there isn’t any distinction between our remote team members and those who work in the office. Of our team, 9 live in Chicago near our physical office and 11 live outside Chicago — a few even outside the US. All of us have the same freedom to work where we feel most comfortable. Even those of us that live in Chicago work outside the office much of the time.

    What that does is create parity and a culture of work where location doesn’t matter. There are no advantages for people who come into the office, no disadvantages to staying home to get your work done. I’ve worked with companies where remote team members were an afterthought. They had to sit through meetings on the other end of a speaker phone while the rest of the team met in-person. The team members who weren’t permitted to work remotely resented those who were, despite the remote team’s obvious second-class status. These days, I live over 500 miles from my nearest coworker but I don’t feel like I’m missing a thing.

    My friend’s team learned a similar lesson. They found that making even their team members in the main office work from home leveled the playing field. The local team benefitted from the productivity of working in isolation and learned to embrace the same constraints as the remote team. That taught everyone how to communicate in a location independent way, making the entire team more effective.

    Lack of parity for remote employees is certainly a big factor when a company tries and fails to integrate remote team members. Most any team can benefit from some time to work outside the office. Let your local team reap the benefits and open your company to a vast pool of talent by hiring the best no matter where they live.

  • Using CSS sprites with Rails helper methods

    We are constantly looking for ways to make our products faster so recently we spent some time optimizing UI graphics in Basecamp. With better support for CSS3 properties in the latest browsers and solid techniques for progressive enhancement, we began by eliminating some graphics altogether. We found that subtle gradients and drop shadows can be completely rendered with CSS properties and many times aren’t missed when viewed with a browser that doesn’t support them — the very definition of progressive enhancement. Using CSS instead of these graphics results in fewer HTTP requests to our servers plus browsers draw native CSS elements much faster than images.

    Another approach we’ve used is CSS sprites, a method for combining many graphics into a single image which is then displayed via CSS. For us this technique reduced dozens of HTTP requests into one — a single, cache-friendly image file. For those new to the technique, the stylesheet references the coordinates of the desired graphic inside the image file. But keeping track of coordinates and creating new CSS styles everytime we wanted to use a graphic would have added a lot of code and made maintenance a chore.

    We wanted to keep the code as easy to write as the Rails image_tag method that we used previously. So this:

    <%= image_tag ("email.gif"), :class => "email" %>
    

    …became this:

    <%= image_sprite :email, :class => "email", :title => "Email" %>
    

    The image_sprite helper method contains the dimensions and coordinates for each and renders the HTML. Here’s a shortened look at the method:

    def image_sprite(image, options = {}) 
        sprites = {
          :add_icon           => {:w => 16,   :h => 16,   :x => 0,    :y => 0},
          :email              => {:w => 26,   :h => 16,   :x => 41,   :y => 0},
          :print              => {:w => 25,   :h => 17,   :x => 68,   :y => 0},
          :trash              => {:w => 10,   :h => 11,   :x => 94,   :y => 0},
          :comments           => {:w => 13,   :h => 13,   :x => 105,  :y => 0},
          :comments_read      => {:w => 13,   :h => 13,   :x => 120,  :y => 0},
          :comments_unread    => {:w => 13,   :h => 13,   :x => 135,  :y => 0},
          :rss                => {:w => 14,   :h => 14,   :x => 150,  :y => 0},
          :ical               => {:w => 14,   :h => 16,   :x => 166,  :y => 0},
          :drag               => {:w => 11,   :h => 11,   :x => 360,  :y => 0},
          :timeclock          => {:w => 17,   :h => 17,   :x => 375,  :y => 0},
          :timeclock_off      => {:w => 17,   :h => 17,   :x => 392,  :y => 0}
        }
        %(<span class="sprite #{options[:class]}" style="background: url(#{path_to_image('/images/basecamp_sprites.png')}) no-repeat -#{sprites[image][:x]}px -#{sprites[image][:y]}px; width: #{sprites[image][:w]}px; padding-top: #{sprites[image][:h]}px; #{options[:style]}" title="#{options[:title]}">#{options[:title]}</span>)
      end
    

    Keeping the image details in a helper method made it easy to convert the existing images to sprites, easy to re-use the sprited images throughout the app, and will really pay off anytime there are changes to the images. Update the sprite image, update the helper, and the change is done everywhere. This improvement has already been rolled out for Basecamp and we hope to streamline our other products using these techniques soon.

  • Fresh wounds lead to speedy bandages

    Since January, we have been experimenting with a new way of working. Two programmers and one designer form a team and take on projects in short, mostly two week, iterations. It’s been a great success so far and has resulted in a huge list of new features across all 37signals products. But after two full terms of new features, one of our teams decided to start the third term with a little spring cleaning.

    The three of us had something different to bring to this term — each of us had been focusing mostly on customer support recently. Jamis and Jeff were fresh off terms in our support programmer role and I had spent the last few months working daily to help customers on our Answers forum. That gave us a unique perspective on our products. We came together having each experienced all of the little things that can be big headaches for our customers. So we spent the past two weeks fixing, polishing, re-writing, and improving the places that we’d seen the most confusion from customers first hand.

    Here is some of what we were able to accomplish:

    Sign up/Sign in:

    • Re-wrote and re-designed the sign in error states so that we could explain in context why someone’s login may not be working.
    • Improved the sign in link to make it more visible when creating new product accounts.
    • Added better detection and prevention of duplicate signups.
    • Cleaned up and fixed various display issues with 37signals ID email notifications.
    • Modernized the sign in screens using CSS instead of transparent PNG images.

    Basecamp:

    • When inviting new users, detect duplicates. This warns admins that they may be trying to invite someone who is already on their account avoiding multiple sign in confusion.
    • Stop trying to automatically create users on an integrated Campfire account and simplify the process—members enter chats as themselves, non-members enter as guests.
    • Fixed that the responsible party pulldown for to-dos shouldn’t include people who can’t see private items
    • Fixed a longstanding issue with reordering of To-do templates.
    • Corrected the decimal precision of the total number of hours displayed on time pages.
    • Resolved various display issues, from text formatting to icon alignment.

    Highrise:

    • Updated monospaced font styles to render more consistently and attractively across browsers and platforms.
    • Updated and improved iCal and API authentication copy.

    Backpack:

    • Exposed better invoice options, such as the email address and ‘Bill to’ field on the Account tab.
    • Introduced per-user iCal feed to fix a recurring time bug whenever DST changes.
    • Fixed an annoying issue with editing multi-day events that could result in the start date being incorrectly set to next year.

    Improving our products isn’t just about new features. Polishing, re-writing, fixing, and improving existing features can do just as much to make them better and more enjoyable to use. Many of these fixes were directly related to repeated questions or suggestions from our customers so we’ll be keeping an eye on support to measure their impact.