Celebrate the web by using another browser than Google’s Chrome

I like Chrome. It’s a great browser. But it’s not so good that it deserves to be the only browser. And that’s the unfortunate opportunity we, people browsing the web, are opening for Google by so overwhelmingly choosing to use it in face of the alternatives.

And this is what we get by doing so: DirecTV just announced that they’ll be turning their website into a Chrome desktop app on June 1st. They don’t actually say that, but that’s what they mean. You can’t call directvnow.com a website if it only works in a single browser.

You don’t have to be that old to remember the dark days when Internet Explorer strangled the web by its utter domination. When large swaths of the web was only accessible through Redmond. Those were not happy days.

Ironically, it was Google’s Chrome that helped fight back the scourge of Internet Explorer’s monopoly. Well, that plus the utter neglect and contempt Microsoft showed the web in those years after they had cut off the air supply to Netscape. Would you believe they even disbanded their browser team after they had conquered the competition? Yup.

Why on earth would we want to go back to such arid times? Nobody wins when the beancounters at companies like DirecTV can eye the browser market shares and justify turning their back on the open, standards-backed web to embrace a few cents on the dollar supporting only the victor.

But you can stop it. By balancing the browsers, choosing to use not just what’s convenient, but what’s lesser used, you can make the business case for monopoly plays a bad deal. Consider it your civic duty as a fan of the open web.

It’s never been easier on web developers to support evergreen browsers. You no longer have to cover every variation of every flavor. Good browsers update automatically. And good browsers support open standards to a degree a developer in 2005 would have cried to have.

So please, if you’re using Chrome, take a moment to download another browser and incorporating it into your routine. I personally love Safari and use it for the bulk of my browsing (with Chrome as a pair for development). But the good folks at Firefox deserve your usage just as much.

Oh, and if you’re a customer of DirecTV, please tell them what you think about their short-sighted move on Twitter. I hear they just love to get feedback!

Jousting with Jekyll

One responsibility of the support team here at Highrise is to maintain our Extras page.

What’s the Extras page?

It’s a list of all the 3rd party products that integrate with Highrise. Almost all were built by the 3rd party using the Highrise API.

This page is important for current and future customers because people use more than one product to get their work done. And these integrations can often save people tons of time.

But it became an absolute pain to manage for us.


There are a whopping 63 different listings on the Extras page right now. Requests to add new listings, update current listings, and remove old listings started to add up.

The Highrise marketing site is maintained using the static-site generator Jekyll. It gives our team control over our content, it works fast, and it’s not a feature heavy dynamic CMS like WordPress.

Jekyll is simple. And powerful . . . if you know how to use that power.

The Extras page was just a giant HTML page in Jekyll. All listings were written in HTML, and if you needed to update a listing, you had to edit the repetitive HTML file and find exactly what line needed to be updated.

This led to manual errors. Typos in HTML. Incorrect links. A lot of wasted time to make tiny changes.

Enter Jekyll data files.

Data files give a middle finger to repetition. You can set custom options and load custom data to make your life much easier.

Here is a short video of why we made this change with an example:

How to use data files in Jekyll

First, create a folder in your repo titled _data and save it. This is where you’re going to store your files.

Files can be in .yml, .yaml, .csv, .json format. We’re using .yml in our example.

Now, create a file you want to store in the _data folder. We’ve created the highrises.yml file.

Here is an example of one of our data files in /_data/highrises.yml format:

- name: Highrise iPhone App
link: https://highrisehq.com/apps/ios
image: extras/img_iphone_app.png
description: Collaborate on contacts, notes, and tasks all from your iPhone.

- name: Android
link: https://highrisehq.com/apps/android
image: extras/img_android_app.png
description: Collaborate on contacts, notes, and tasks all from your Android.

The data can now be accessed usingsite.data.highrises in our HTML. The filename highrises determines the variable name.

This information can now be used in your templates or HTML files.

For example:


Using site.data.highrises in our file, Jekyll will insert the information from the data file.

Data files have saved us lots of time here at Highrise. Other examples of where you might use them:

  • accessing different authors’ bios of your blog
  • posting store hours for your brick-and-mortar shop
  • ordering any list of products you’re selling

If you’re interested in learning Jekyll, we recommend checking out the tutorials here and the community here.

If you enjoyed this post, please tap the 💚 and share it with others. And check out Highrise. CRM systems are cumbersome and take too much time. We designed Highrise, so you’ll be a master within minutes.

Just starting out? Ditch the “full stack developer” label

The words you use to represent yourself matter — and those words mean nothing.

The only time “full stack” means something. 😍

The vagueness and confusion around the phrase “full stack developer” has been lingering for years. Google it and you’ll find plenty of discussion about why it’s such a loaded term.

Given that long-standing vagueness, labelling yourself as “full stack” might be doing you more harm than good, especially if you’re just starting out.

🥞 Are you being honest?

“Full stack” basically implies that you can do it all — that you can build front to back effectively and ship.

But can you really do all of that well?

Anyone with some programming experience can learn the basics of something new and cobble a solution together. But that certainly doesn’t make it good software — a goal every good, experienced programmer strives for.

When someone with a few years experience labels themselves as a “full stack developer”, I’m pretty skeptical. Is there really enough experience there to be good at everything? I’m not saying it’s impossible, but it’s certainly not likely.

Not to mention, what’s a “stack” in 2017 anyway? HTML, CSS, Javascript, Rails, Node, PHP, Go, Python, React, Angular, MySQL, Oracle, Swift, Kotlin, Android, iOS, .Net, Java, jQuery, Mongo, Redis…about a thousand other things?

Here’s the important thing to remember — knowing your limitations isn’t a sign of weakness or impostor syndrome. It’s a sign of honesty, and that in itself is a major strength.

Don’t sell yourself short, but don’t be afraid to acknowledge your (current) limitations either. People will respect you for it.

🥞 What matters to you? What’s your focus?

Referring to yourself as “full stack” doesn’t express any opinions or preferences — it’s vague, broad, and bland. It’s the equivalent of saying “I’ll do whatever work, it doesn’t matter to me”.

And if that’s the case, well, stick with that label. But if the work does matter to you (and it certainly should), speak your mind.

If Rails is your favorite technology, say so — be proud of it! If Javascript is your thing, huzzah! Just don’t fall back to labelling yourself with a bullshit buzzword that everyone else uses.

Saying you’ve been “A proud, productive Ruby on Rails programmer for two happy years” sounds a hell of a lot better (and means a lot more) than “full stack developer”.

Along the same lines, don’t overdo it when presenting your skills.

“Full stack developers” are often the same folks who list out the dozens and dozens of skills they have. Whether they have those skills is irrelevant — it gives the appearance of a quantity-over-quality cover story.

When listing out your strengths, be sure to keep the list short and focused. A handful of really strong talking points is far better than a wall-of-text laundry list of skills. It demonstrates clarity in your thinking and a healthy opinion on what matters to you.

The bottom line is this: the words you pick to represent yourself really matter, especially when you’re just starting out. “Full stack” is far too bullshitty to do you any good.

Figure out your limits, be honest, and focus on what you enjoy most. Express those in your work and how you talk about yourself. You’ll have a clearer head and will be positioning yourself for a much happier, long-term programming career.

If this article was helpful to you, please do hit the 💚 button below. Thanks!

We’re hard at work making the various stacks of Basecamp 3 better every day. Check it out!

Join Basecamp as our new Rails programmer

Basecamp is hiring! We have a rare opening for a Rails programmer to work on new product development within our General Practice team. We haven’t had an opening for this kind of work for a few years, so we’re excited to welcome someone new to the team!

This is a position for an experienced Rails programmer, but you don’t have to be a rock star, a ninja, or a superhero to apply. In fact, if you self-identify in any of those categories, we’d rather you don’t!

We’re looking for someone with a strong track record of putting Rails to work and bringing products to life. This is not a junior position, but, imposters everywhere, this is in reach to YOU. If you ship solid work, you have the experience we’re looking for.

We want strong, diverse teams built from different backgrounds, experiences and identities. We’re ready for the ongoing work that goes into building an inclusive, supportive place for you to do the best work of your career. That starts with regularly working no more than 40 hours a week and getting 8+ hours of sleep a night. Our workplace and our benefits are designed to support a sustainable, healthy relationship with your work.

Today, our team works from 32 different cities spread across 6 countries. You can work from anywhere in the world, so long as your normal working day has 4 hours or more overlap with Chicago time (CST/UTC-6). Nomads welcome.

About the Job

The General Practice group at Basecamp works on a regular cadence of 6-week cycles in either Big Batch or Small Batch mode. Our feature teams are small. You’ll usually work with one designer, sometimes another programmer, and usually a tester as well.

That’s just a handful of people expected to regularly ship a big feature in 6 weeks or a small feature in a week or two. This is a fast pace, but it’s never frantic. We don’t have time for harried, hurried work. We value a calm company and deliberate, concerted effort. We work 40-hour weeks here (and less in the summer!). This is not a job for workaholic heroes who thrive on 80-hour+ marathons. NOPE!

Our formula: Capable people left to get great work done in a reasonable amount of time with minimal distractions.

And, of course, you’ll have the full support of some of the best Rails programmers in the business. We literally wrote the framework. Everyone here is keen to help and support newcomers to become part of the team and, ultimately, to do the best work of their career.

While creating new features for Basecamp 3 is mostly greenfield work, there are also a fair share of legacy obligations on our plate. We have the first Rails application ever created still running and serving happy customers. You may well work on the first version of Basecamp or any of the other products we no longer sell but still lovingly support. We cherish our legacy. Take heart when `git blame` reveal lines 12 years or older 😀.

You’ll help us support customers directly. Everyone at Basecamp participates in Everyone On Support every few months, and you’ll regularly help with escalated technical issues.

We’re also staunch supporters of the open source community. Not just through Rails, but plenty of smaller projects as well, including quite a few in JavaScript. Every programmer at Basecamp is encouraged to give back and share our tools.

About You

In broad strokes, Managers of One thrive at Basecamp. We’re committed generalists, eager learners, conscientious workers, and curators of what’s essential. We’re quick to trust. We see things through. We’re kind to each other, look up to each other, and support each other. We achieve together. We are colleagues, here to do our best work.

We’re all quite different people (and we stumble, fall, reach, and achieve just like anyone!) but these are fundamental attitudes we share. What floats your boat? What lights a fire in you? What keeps it burning?

We aren’t looking for ideological clones, but we are looking for people who share our basic values and beliefs about how to write good software. It helps if you can identify as a software writer. If you care about writing clean, concise code. If the thought of a majestic monolith appeals to you. If you seek the epicenter. If you say no.

As an experienced Rails developer, you should be intimately familiar with the framework, with Ruby, and with the stables of full-stack web development: HTTP, JavaScript, CSS, HTML, SQL. It’s a bonus if you’re broadly familiar with other languages as well — we write our iOS app in Swift, our Android app in Kotlin, and have tooling written in Go — but your main work will be Ruby through and through. We ❤️ Ruby, if wasn’t clear by now 😄.

Benefits & Compensation

Our pay is in the top 5% — or better! — of the industry for the matched role and experience, based in Chicago. No matter where you live. Plus, with two years under your belt, you’ll participate in our profit-growth sharing program.

Our benefits at Basecamp are all about helping you lead a healthy life away from work. While we have a lovely office in Chicago, it’s not where you’ll find foosball tables constantly spinning, paid lunches, or any of the other trappings that companies use to lure employees into staying ever longer at work.

Work can wait. Our benefits include 4-day Summer Weeks, a yearly paid vacation, a one-month sabbatical every three years, and allowances for CSA, fitness, massage, and continuing education. We have top-shelf health insurance and a retirement plan with a generous match. See the full list.

How to Apply

Please send an application tailored to this position that speaks to us. Introduce yourself as a colleague. Show us that future.

We value great writers, so please do take your time with the application. Forget that generic resume. There’s no prize for being the first to submit!

We’d like to see examples of software you’ve written. The actual code. We appreciate that it can be hard to share representative samples when you’ve been working on commercial software, so anything you can scrape together will be good. If you have open source contributions, those are great candidates, but you don’t have to be an open source contributor to apply.

You can share private repos on GitHub with @dhh, @packagethief, @sstephenson, and @jeremy. They will be your gentle and kind evaluators on the programming side of things.

Go for it!

We are accepting applications for this position until February 27, 2017. We’ll let you know that we’ve received your application. After that, you probably shouldn’t expect to hear back from us until after the application deadline has passed. We want to give everyone a fair chance to apply and be evaluated.

As mentioned in the introduction, we’re eager to assemble a more diverse team. In fact, we’re not afraid of putting extra weight on candidates from underrepresented groups at Basecamp.

We can’t wait to hear from you!

(And again, imposters: We are too. Take heart. Step up.)

We’re looking for a support programmer

We’re looking for a support programmer to work with us as we build a safer, faster, better Basecamp. As well as working on Basecamp and our other apps, you’ll be an important part of our work on Basecamp, the company. You’ll be joining our existing support programmer (me!) and working as part of our Security, Infrastructure and Performance team in a fun and varied role that will help you to develop personally and professionally.

We want strong, diverse teams built from different backgrounds, experiences and identities. We’re ready for the ongoing work that goes into building an inclusive, supportive place for you to do the best work of your career. That starts with regularly working no more than 40 hours a week, and hopefully getting 8+ hours sleep a night. Our benefits are designed to support a sustainable, healthy relationship with your work.

Currently our team works from 32 different cities, spread across 6 countries. You can work from anywhere in the world, but your normal working day should have 4 hours or more overlap with Central Time (CST).

About you

Do you have questions?
Do you like finding answers?
Does helping people make your heart sing?
Can you approach problems calmly and compassionately?
Do you think clearly, and can you express yourself in English and in code?

About the job

Here are some examples of the kind of work you’ll be doing. You might not know how to do everything below, but you do know how to start looking and learning. Every single day, you’ll get to work across teams to support three major areas:

Making our customers happy.

  • You’ll be helping our customers to perform tasks they can’t do easily in Basecamp itself. We refer to these as concierge-style requests. They often involve writing small snippets of Ruby and working in a console
  • You’ll make it easier for companies to trust us by answering security and compliance questions
  • Every day you’ll be supporting our wonderful support team, to help them to ask better questions and find better answers for our customers
  • Developers who are working with our APIs sometimes need some help too, and you’ll be on hand to offer the right documentation, and suggestions on the best way to approach things

Working on our legacy

In the pursuit of answers, you’ll go delving into the codebase for all of our apps (including the very first Rails app!) to discover how things work. You’ll be comfortable looking at code, exceptions and logs, identifying problems and suggesting fixes.

Experience with Ruby, Rails and JavaScript would be very helpful, but if you have been using different languages and stacks, that’s fine too. Be sure to tell us all about it in your application.

As well as spelunking through our apps, you’ll work with people across Basecamp every day:

  • Working with our ops team to track down mail delivery issues, or networking problems or SSL certificate fun
  • Working with programmers to triage bugs, suggest fixes, and write code
  • Exploring issues with our Android or iOS apps, and working with our mobile teams
  • Support our QA team as they lead our feature retrospectives

Helping us find ways to be better

  • Triaging security reports & identifying fixes
  • Looking at reports of slowness in our apps and searching for the cause
  • Researching & planning improvements across teams, like ending support for RC4, or taking part in PrivacyShield certification
  • Looking at exceptions in our apps, and identifying fixes
  • Providing thoughtful commentary on product pitches, ideas and suggested fixes based on what you have seen
  • Investigating common cases, and coming up with ideas on how to reduce them

You’ll get support and trust every step of the way, and the freedom to make decisions to make things simpler, clearer, easier and more honest.

If this sounds like the kind of work you’d love to do, please apply. We’re especially interested in applications from folks in the early stages of their programming careers. If you know someone who would be perfect, tell them to apply! We’re accepting applications until February 3rd, 2017. Your cover letter is your chance to shine, so take it!

Learning and mastering isn’t the same

When I first began making applications for the web, getting started was hard. Just figuring out which components to download, how to configure them, and getting Hello World through it all was a daunting affair. Frameworks like Ruby on Rails changed that, and now it really is possible to animate a complete database-backed web application in 15 minutes or less.

That means these days you can go from (almost) zero prerequisites to a (sorta) working software prototype in a bootcamp’s worth of introduction material. That’s amazing. Basic proficiency has never been more attainable or approachable.

Given this leap, it’s no wonder that people mistake the beginning for the end. That getting started is the same thing as knowing it all. But it remains a completely unrealistic expectation, and thus a mistake. Building a complete information system, like, say, your Basecamp or Shopify or GitHub or Zendesk remains real work that requires deep skill. It’s foolish to pretend otherwise.

And perhaps we have pretended otherwise at times. I’m certainly guilty of focusing on just how easy we made getting started that the conversation often didn’t extend to the work it takes to finish. Simply because that’s what felt like the main obstacle to getting more people onto the path of learning at the time.

Now that this obstacle is mostly cleared away, it’s time to focus on the latter part of the discussion: Becoming really good at anything takes time. Web development is no different, not even with Rails. We’ve changed the game from “hard to learn, hard to master” to “easy to learn, hard to master”. Yes, the second part remains the same.

I’m still learning. I’m still getting better. And I’ve been at this web development game for damn close to twenty years (if you count when I started dabbling with HTML/CSS). That doesn’t at all mean it’ll take you twenty years to become good at it, but it probably does mean that you should have realistic expectations about what you can learn in three or six months.

And it’s not so much about how long it’ll take you to learn your way around the framework or the language or the ecosystem. It’s as much as how long it’ll take you to become an expert. It’s one thing knowing there are 10 different ways to do a thing; it’s another to know which is a better fit and when.

This is part of why I like to compare writing software with writing prose. Most people in the developed world will have basic proficiency writing their native tongue by the time they finish high school. But how long does it take to become a great writer? Longer than that. Much longer, in most cases. Few find that surprising.

Yet plenty of people do seem to be genuinely surprised that a developer who just picked up Ruby on Rails, or any other extensive framework, language, or ecosystem, doesn’t make all the right choices the first time they’re faced with them. And that’s just plain silly.

Let’s continue to celebrate how easy we’ve made getting started, but let’s also set a realistic timeline for mastery. Not to scare anyone off the journey, but to prepare them for it. A glorious, years-long journey of learning. It’s a lot of fun if you know what you’re in for.

Feeling Safe Across Data Centers

The Single Server Room

In a perfect world all of your servers are hard-wired to each other in a room. There would be one opening with which the world connects to your little slice of the Internet. That way all of your cross-service communication into databases, infrastructure and other services happens within the single set of servers all directly connected.

In the modern world we often have to reach across the Internet to access services and applications. This can be an awkward feeling and presents some unique problems. However, there are a few techniques and patterns you can use to make it a little less frightening. Let’s talk through some of the bigger concerns and what you can do about them.


One big reason reaching across data centers, even for first-party systems, can be an issue is the matter of security. There’s a base level of security you lose sending data and commands across the Internet where anyone can glance at your requests and try to decode the data or alter what’s being sent. All someone needs is a basic understanding of networking, attack techniques like Man-in-the-middle and perhaps Wireshark to unpack your cross-service request, see sensitive data, tinker with the request and send an altered request to the final destination. Fear not, however, there are some standard techniques to mitigate this risk:

1. SSL

Always communicate over SSL when you’re sending requests back and forth over your systems. This is a straightforward, standard way to secure communications between two services or entities on the Web. Under the hood, SSL uses Public/Private Key encryption to secure the body of a request between two entities. Reddit, Facebook and all of your financial institutions use SSL (HTTPS) to communicate with your browser, and likely when they communicate between internal services. Its become far easier and cheaper (free) to get SSL for your services as well thanks to organizations like Let’s Encrypt.

2. Request Signing

While communication over SSL is somewhat secure, it can fail. Or perhaps you don’t need SSL to prevent snooping, but you do want to ensure the data wasn’t tampered with. At Highrise we decided to utilize a drafted standard that is being worked on currently under IETF, which outlines a method for signing a request. This means you can use an encryption algorithm and set of keys that you configure to define a formal verification for the content of your request. Let’s say I want to ensure that the Digest, Authentication and Date headers were specifically never altered. By following this protocol I would: Set up the request, retrieve the signature (using signing keys) for the specified headers, add the signature to the request and execute the request. This standard allows for specifying what keys you used to sign the request (via a KeyId parameter), which headers were signed, and which algorithm was used to do the signing. The recipient server can use this information to verify the contents of those headers were not altered during transport. The details of this freshly forming protocol go a fair bit deeper and are worth understanding. There will be a followup post directed at this topic shortly.

These two protocols give us a stronger confidence in the things being sent over the wire to other services.


Speed of accessing external services due to network fluctuations as well as actual downtime are facts of a cross-data-center world. Obviously, both types of issues can compound themselves and start making whole services virtually unusable. You often won’t be able to stop these things from happening so you have to prepare for them. Let’s talk about four mitigation techniques:

1. Local caches, Avoid making requests

Caching or intelligently deciding when to request across services can cut down on the number of actual requests you need to make. Things like eTags can help with this as well as expiration headers or simply not requesting data unless you absolutely need it to accomplish your task. If the thing didn’t change from the last time it was requested let the client reuse the data it already has.

2. Timeout Retry

I mentioned earlier that slow responses from external services can create a compounding problem for your system. You can mitigate this risk by planning for it to happen and wrapping specific patterns around your communication. Specifically, set reasonable timeouts when you make external requests. One problem with timeouts is that you can’t tell if it ever reached the server. So you should plan to make your endpoint idempotent whenever possible. Idempotent endpoints make retries simpler as well, since you can just keep hitting the endpoint and expect no unexpected change. Finally, you maybe should slow down rescheduling the request to give some time for a system to recover or avoid hammering the service. This is called exponential back-off.

At Highrise, certain important requests have a timeout like 1 second. If the request fails it will be retried 3 times before it stops trying and starts messaging our team about issues. Each time it will schedule the job to retry further out: 3 seconds after failure, 9 seconds after failure and 27 seconds after failure, because of the exponential back-off algorithm. In cases where something is, for instance, sending an email via an external request, idempotency is a very serious concern so that you avoid sending the exact same email 3 times because of retries. You can accomplish something like that with a key that the server uses to decide if that operation has already been accomplished.

3. Circuit Breakers

Circuit Breakers paired with timeouts can help you both better handle full-service degradation and provide a window for recovery. A Circuit Breaker basically lets you define a set of rules that says when a breaker should “trip.” When a breaker trips you skip over an operation and instead respond with “try again later please,” re-queue a job or use some other retry mechanism. In practice at Highrise, we wrap requests to an external service in a circuit breaker. If the breaker trips due to too many request timeouts, we display a message to any users trying to access functionality that would use that service, and put jobs on hold that use that service. Jobs that were in-flight will presumably fail and be retried as usual. A tripped breaker stays tripped for several minutes (a configured value) and thus keeps us from hammering a service that may be struggling to keep up. This gives Operations some breathing room to add servers, fix a bug or simply allow network latency to recover a little.

4. Upcheck

Upchecks, Health-Checks and the like are very useful to get a basic understanding of whether you can reach a service. Libraries standardize some of this for you so you don’t have to think much about what to provide. Really what you want is to understand whether you can reach the service and if its basic functions are operational. Upchecks paired with a circuit breaker can help decide whether to show a maintenance page or to skip jobs that won’t work at the moment. These checks should be extremely fast. At Highrise for our first-party, external services we check once on each web-request for the livelihood of a feature about to be accessed. Again let’s say we have an external emailing service. If someone goes to the email feature we wouldn’t check at each email operation, in the code, that the service is up. Instead, we would check at the beginning of the web request if the email service is up. If it is up continue to the feature, if it isn’t display a basic “down, please try later” message.

Act like it isn’t yours

When it comes to external services, even if you wrote it, you have to act like you have no control of it. You can’t assume any external service will always operate normally. The reality is you have limited control, so you have to design a system that explains issues to your users as they happen and mostly recovers on its own. Protect what you can control and avoid requiring humans to repair issues like this. The more your computers can recover on their own, the more you can worry about the next feature, the next user or the next beer.

I’m a Software Engineer at Highrise (@highrise). Follow me on Twitter to tell me what you think, as well as find more ramblings about Software and the world by me @jphenow.

Myth debunking: WebViews suck, everything should be native

Contrary to popular belief, Android webviews aren’t your enemy — they’re your best friend

If there’s one thing that gets a lot of shit on Android, it’s webviews (OK, not as much as fragments, but it’s up there).

How do I know? The next time you talk to an Android programmer or designer, try this little experiment: tell them you use a lot of webviews. Then wait for the confounded looks on their face and the subtle “ouch, sorry” nodding of the head.😲

The reason people feel that way is because we’ve been conditioned to think the “right way” to build apps is by making everything a native screen. Surely no serious, self-respecting native app developer would use a bunch of webviews!

To the contrary! Webviews are wonderful and offer us incredible scale, flexibility, freedom, and happiness.

200 screens, 2 programmers, 1 designer, 40 hour weeks, 3 vacations, 1 sabbatical

Being a small company is something we hold dear at Basecamp — it’s a core value of the company.

But Basecamp 3 is not a small app — its feature set is both broad and deep. It has well over 200 unique screens, and new ones are added every week.

And yet with so many features and screens, Basecamp 3 for Android was still built by only two programmers and one designer.

In earnest, the Android app started development in March 2015 (when we assembled our full team), and it launched at the beginning of November 2015. That’s roughly 0 to 100% in 8 months.

The key to shipping was that we leaned on the webviews where we needed to.

While the Android team was building native functionality and views for high-touch screens (things like push notifications, navigation, the home screen), a whole bunch of other webview screens were built by our web team. They had our back, building high-fidelity, mobile-friendly webviews that our app could use.

For the sake of argument, let’s do some back of the napkin math to see how important webviews were to Basecamp 3 for Android. If we assume each screen takes an average of 3 days to build/test natively (which is a highly aggressive estimate, but let’s go with it), the math would work out to:

200 screens x 3 days per screen = 600 days
600 days / 2 programmers = 300 days per programmer
300 days / 20 work days per month = 15 months

15 months! That would have been a mere 7 months overdue. Or if you wanted to be a real sadist, we could assume 30 days a month of work, and that would get us to 10 months! 😭

And let’s not forget, this also assumes 1) no new screens are added 2) no screens are ever changed 3) we are 100% efficient every single day and 4) we don’t take a single day off. Yeah right!

The math simply didn’t add up. To make the math work, we had to rely on webviews.

The result: we worked 40 hour weeks during the cold months, and 32 hours weeks in the summer months. We took our vacations and sabbaticals. We lived our personal lives. And on launch day, we had full coverage of Basecamp 3, on-time, in-scope, and without burning anyone out.

If your app is moderately large, and keeping your team small and sane is something your team values, you need webviews.

Programmer happiness and native level-ups

One argument the all-native camp makes is that native screens are simply better all around. But better for who?

Sure, you could argue that a native screen provides the best interaction experience for customers. And whatever is best for a customer, it’s certainly worth doing, right?

But it’s not really that cut and dry.

What’s best for the customer sometimes means doing what’s best for your team. And what’s best for your team is when you’re happy — when you’re excited to work on something and given autonomy over your work. That’s when you produce your best work for a customer.

Webviews give us the flexibility and freedom to choose what screens to level up as native views, instead of being forced to make everything native.

This allowed us to focus on screens we knew would get used a lot (like our home screen) and really fine-tune those. Not only was that good for our customers, it was good for us. Those screens are a ton of fun to build.

Now imagine the opposite scenario — having to re-make native screens for every admin, settings, or text-heavy instructional screen that’s already been made on the web. As a motivated designer or programmer, how quickly would you get bored with that kind of copypasta work? Those aren’t the kinds of things that get intrinsically motivated teams out of bed in the morning.

The freedom to choose our native level ups was a true godsend, and was only possible because we had full webview coverage for everything else.

You don’t need 100% native fidelity on every screen

I can hear the haters — “Sure, you can use webviews for everything, but everyone can tell they’re webviews and not native”.

So what?

Does every single screen need to have 60 fps animations, with activities that launch from the exact x/y coordinate where my finger touches the screen? Does that kind of fidelity matter on an admin screen? Does it matter to people who are just trying to get their work done if a screen follows the Material Design spec exactly?

Look, I’m all for great design and attention to detail. We take it seriously and I respect it immensely.

But everything is a tradeoff. And under the right circumstances, am I willing to trade a little native fidelity when we can get 90% of the way there with a webview? Absolutely.

Webviews with Turbolinks 5 enabled are plenty fast

Critics of webviews may point to their page loading performance as a reason not to use them.

But with the introduction of Turbolinks 5, the performance of webviews is pretty much a non-issue.

It doesn’t give you carte blanche to make ridiculously heavy webviews, but it sure will carry you pretty far even if you do.

To take advantage of our Turbolinks enabled webviews in Basecamp 3, we built and open-sourced Turbolinks Android, a library that makes working with Turbolinks trivial. Just add one dependency, then tell the library to load your page:

protected void onCreate(Bundle savedInstanceState) {

That’s basically it. Turbolinks Android takes care of the rest — providing you with everything you need for ultra-fast page loads from a single, low-memory webview instance.

Scaling our apps with independent, complementary teams

Webviews give us immediate and amazing scale across platforms. Instead of using the efforts of just two Android programmers, we can harness the power of all thirteen of our programmers together.

Every team working on a feature has the capability to not only build new stuff, but to deploy them to all of our customers quickly with minimal coordination across teams and zero red tape.

Once a team has tested a new feature on the Android app, they deploy it without any intervention from us. Every customer immediately benefits from the new feature on the web and in the app.

This is a wonderfully independent, yet complementary way to work. No team is ever stymied in deploying new stuff to customers, and every platform, including our Android app, automatically improves.

So there you have it — we’ve reaped a bunch of amazing benefits from webviews, a component that gets more scorn than love in the Android world.

But remember, one size does not fit all. The techniques and styles that work for some teams might not fit your team well.

Continue to challenge conventional viewpoints that don’t feel right to you (including this post)! The most important thing is to find what fits your team’s values and build around those. 🤘

We’re working really hard to make the all-new Basecamp 3 and its companion Android app as great as they can be (using webviews, of course) 😉. Check ’em out!

If this was helpful to you, please do hit the heart button below or let me know on Twitter. Thanks for reading!

Ruby has been fast enough for 13 years

When I started programming Ruby, it was on an Apple iBook G4/800. That beautiful 12” powerhouse of a 800 MHz PowerPC with a rocking 256MB of RAM. A lovely computer that was not only fast enough to run Ruby, but a pleasure to develop the first version of both Rails and Basecamp on.

When Basecamp launched in February of 2004, we ran on a single shared Linux server at Tilted. I don’t fully remember the CPU spec, but I do remember that we had the same 256MB of RAM available. And I believe the monthly cost for this princely server was around $349. It was not only fast enough to run Rails and Basecamp, but good enough to do so on its own for more than a year while we built a business that could pay the salaries of four.

So forgive me if the zombie theme of “Ruby is so damn slow” isn’t striking a recognizable tune with me. Now, I don’t for a minute doubt that Ruby may well be too slow for some people doing some things. But given the fact that Ruby was plenty fast for me in 2003 on a bootstrapped budget with the performance of the day, I think perhaps that tune is out of key for many others too.

We built an application used by millions of people that has made tens of millions of dollars in real money and continues to grow and prosper. No, it’s certainly not Internet Scale™. And it certainly doesn’t prove that Ruby is fast enough to be economical when you get there, but it does lodge an anecdote that it’s probably more than fast enough for most people doing most things.

Don’t get me wrong, though. I love speed. I especially love free and cheap speed. It’s just that I’m not willing to trade things that are of real, enduring value to get more of a nice-to-have once we’ve long since reached Good Enough. Things like programmer happiness, the eloquence of Ruby, and the productivity of Rails.

Ruby is a luxury in the most egalitarian sense possible. It’s a luxury that the 99% can afford, but the 1% might struggle with. What a wonderful inversion of tradition!

It’s an incredibly affordable luxury for all those businesses where the cost of people, not machines, dominate the balance sheet. There are many such businesses today, and Ruby has never been better for those.

And again. I’m not decrying speed. The ambitious goals of Ruby 3×3, the continued focus to optimize Rails, and the many performance wonks in the community who work tirelessly to make everything faster do great work that I’m thrilled to benefit from.

It’s just that most of it is gravy for most people. Oh, I can save a few extra instances because of this latest patch? Wonderful. Thank you very much! (If only it served as a drop in the bucket to pay the latest increase in healthcare premiums that arrived in 2016.)

Ruby was fast enough in 2003 to build a business like Basecamp with no impediments. Ruby is so much faster and so much cheaper in 2016 it’s ridiculous. On the other hand, skilled programmers have never been more expensive. Splurge on the luxuries you can afford to keep them happy.

Snapback Cache — What we use to make our infinite scrolling feeds at Highrise awesome.

Many apps today have some concept of an infinite scrolling feed: Facebook, Twitter, LinkedIn and many more. Almost all of them suffer from the same problem. If you click on something in the feed that brings you to a new page, when you hit the back button or try to return to that original feed, your place is lost. All the scrolling is gone.

At Highrise we had that same problem. So this is the library we use to fix that. We call it our Snapback Cache, and it’s made a big improvement to how people can use infinite scroll in our app and still get a lot of work done without losing their place.

Another great thing about this is it operates on the URL, so you can have multiple infinite scrolling feeds to cache. At Highrise we have a “main activity” and then activities for a Contact, etc. They each get their separate cache. To keep a manageable memory footprint for your browser, we keep 10 caches as a maximum.

The basics of how it works

Using this small javascript library, you hook it up to the click events on things in your infinite scrolling feed. For example:


Now when people click the links inside our “recordings” container, the stuff inside the current recordings container is cached locally using the browser’s session storage.

Then the javascript library watches the load event of any pages being browsed. If the library sees that that browser’s URL is a url we’ve already cached, and it’s not “too old” (15 minutes), we replace the contents of our container (#recordings in our example) with the cached version, and scroll the browser to the place where it had been cached.

This sounds easy, but there are certain things we bumped into that the library also helps with. Things like disabling autofocus events that mess up scrolling and making sure things in the cache can actually be more granularly ignored or even refreshed.

Syntax and how to use it

var snapbackCache = SnapbackCache({ options });

Here are some example options:


bodySelector is mandatory. It tells us what on the page you want to cache.

finish is a function of things that you’d like to happen before the page is cached to get the page to get cleaned up. For example, we already try to get jQuery animations to finish, but if there’s anything else on the page that might be animated or dynamically changing when someone is trying to navigate your site, you probably don’t want those “transitional” things cached. In our case we have a search bar that we want cleared up before things are cached.

removeAutofocus is a function that removes any auto focus behavior from your page. autoFocus events can mess with the browsers ability to scroll to the right place. So we want to nip that in this function. In our case we have multiple autofocus things going on, so we clear all that up.

refreshItems is a function to help refresh anything that might have gone stale from the cache. You can use that in conjunction with a method available on snpachbackCache called markDirty.

So in our case, we cache a note or comment or email in our feed. But if someone at some point edits/deletes one of those notes, comments or emails, we have javascript call


Then when the snapbackCache replaces the cached contents it’s saving for us, it makes sure to call the refreshItems function you specify along with an array of “dirty items” you can do something with. In our case, we take all those dirty ids, and issue an ajax call that does all the work to refresh bits of the cached page.

nextPageOffset is a function that the Snapback cache can use to figure out what “page” your user is on. We take that page and store it along the cached contents of the page. That way when the cached page is restored you have the page number the user was on and get pick up infinite paging at the appropriate place. See the page-cache:loaded event below to do that.


There are a couple of events we send out that are useful.

snapback-cache:cached is an event emitted as soon as the contents of the page have been cached into session storage

snapback-cache:loaded is an event emitted as soon as the contents of the page have been replaced. We use this at Highrise to set the appropriate offset for our infinite scrolling:


nextPageOffset was calculated because we had setup a “nextPageOffset” function on the page cache.


1) Add the snapback_cache.js to your javascript stack.

2) Add a cache variable with the options set:

var snapbackCache = SnapbackCache({ bodySelector: "#recordings", });

3) Call snapbackCache.cacheCurrentPage() whenever you need to, and magically when people return to that url, the cache will do the rest.


Source code available on Github. Feedback and pull requests are greatly appreciated. Let me know how we can improve this.

A ton of thanks to everyone at Highrise for helping get this into our stack. Especially Jon Phenow, Grant Blakeman and Michael Dwan for the edits and help getting it open sourced.


You should follow us on Twitter: here, or see how we can help you with contact management using Highrise — a handy tool to help you remove anxiety around tracking who to follow up with and what to do next.