You work at thoughtbot. This is your playbook. It details how you and your teammates run our software consulting company and how we make web and mobile products together. It is a living document that you can edit in a private GitHub repo.
It's filled with things we've learned based on our own experience and study of others' experiences.
We've made the playbook free and licensed it as Creative Commons Attribution-NonCommercial so others may learn from, or use, our tactics in their own companies. While our "plays" have worked for us, we trust their judgment over ours to decide which tools and techniques might work for them, too.
Some of our work needs to be private, such as links to various accounts like our HR system or documentation on how we handle credentials. Look in our private company handbook repository for that kind of information.
Some of our work is very technical, but can be public, such as our git protocol for feature branches. Look in our public thoughtbot/guides
GitHub repo for that kind of information.
We regularly eliminate and simplify policies. Our most important policy is "use your best judgement".
We strive for few job titles, few departments, and few hierarchies. We prefer composition of roles necessary for projects and company objectives over inheritance of bosses and direct reports. We are at thoughtbot primarily for our design and development skill, and want to apply it, rather than creating company overhead.
We avoid having private conversations about each other or clients. Instead, we talk in person, and use tools such as Slack, Basecamp, and GitHub to communicate openly within a project, within thoughtbot, and publicly.
While we should be cognizant of people's feelings, and seek to work with each other constructively, we cannot let those concerns get in the way of our happiness and the success of our work. We'd rather be too honest than too polite.
Our standards are very high, and bringing on a new team member requires a "yes" from everyone who participated in the interview process. Therefore, we expect the best from each other, give each other the benefit of the doubt, and encourage each other to take initiative to improve ourselves and the company.
We recognize that we can always be better. Therefore, we have strong opinions, loosely held, and take initiative to improve ourselves, the company, and our community.
We work a sustainable pace. We work four days for clients on consulting and one day on "investment time." We typically spend Monday-Thursday on client work and Friday on investment.
When taking time off during client work, we discuss how it will impact the schedule with other team members.
Sending off-hours communication may create an unintended sense of urgency with the recipients of your message. Try to avoid creating that urgency when possible.
Unless actually urgent, you may ignore off-hours messages which you receive and handle them once you are back at work.
We make our money on consulting projects. Those projects start with sales and go through a normal flow of designing, developing, shipping, monitoring, and iterating. We should do such a good job for our clients that they will want to poach us, and be such a great place to work that we can be confident our teammates won't leave.
Investment time is time for investment in ourselves, our company, and our community. Primarily this means doing something that interests us like learning a new programming language, contributing to open source, discussing interesting things, attending community events, or reading an educational book. The goal is to encourage individuals to improve and share their knowledge with the rest of the team.
We organize our investment work on the "Investment Time" Trello board.
Ideas for Fridays and in between client projects:
Here are some ideas that are more directly revenue generating:
There is a difference between normal Friday investment time, and extended downtime between client projects.
Extended periods of time between client projects should skew heavily towards revenue generating activities. This could be networking and sales, working on existing revenue generating products and services, or creating something new that will generate revenue.
Because this extended time period will go away when you resume client work, and because we can't sustain non-revenue generating activities for long, approach this extended time between client projects with a sense of urgency.
Validating ideas, shipping, and getting to revenue generation as quickly as possible should be a priority. We shouldn't go weeks without results to show, and we should impose the same constraints and process as we do on client projects.
"Most people make the mistake of thinking design is what it looks like. People think it's this veneer — that the designers are handed this box and told, 'Make it look good!' That's not what we think design is. It's not just what it looks like and feels like. Design is how it works." - Steve Jobs
Product Design Sprints, an invention of Google Ventures' design team, are 5-phase exercises intended to improve the chances of making something people want. We want to turn false confidence into validated confidence before beginning an expensive build. Or, we want to dodge bullets by learning we shouldn't begin the expensive build at all.
Sprints are useful starting points when kicking off a new product or workflow, as well as solving problems with an existing product. They typically last 5 days but we have done them in less time. We get as many stakeholders and expertises in the room as we can.
Product design sprints are test-driven design.
Before the sprint, our clients schedule 5 real humans for the tests we'll do in the last phase. They know their users better than we do.
They also gather research from sources such as:
We may also do some (paid) prep-work:
We typically order breakfast for the first day to make it feel special but don't order lunch for each day of the sprint. For both the sprints and normal days, we believe it's important to not have "working lunches", instead breaking from work for a short time to rest the brain, maybe get some fresh air, and interact with teammates and clients.
The exercises in this phase help us understand and empathize with the users' life (consumer software) or work (business software) needs.
Throughout the phase, people take notes, often on sticky notes that they stick to the walls of the room.
We start with "pitch practice", where the client pitches their product like they would to investors. This helps us identify the user, their problem, and the job they are hiring the product to do. It also begins documenting the vocabulary used in the domain.
We then review research from Quora, Analytics, AdWords Keyword Planner, interviews, and surveys. This helps us understand users' motivation, the marketing funnel, and the size of target markets.
Lastly, we sketch what the rest of the sprint will focus on: the critical path for the software. At this point, we try to keep this high-level and as light on implementation details as possible. A great question to help generate the critical path is:
We will edit the critical path as we move through the phases.
The exercises in this phase help us exhaust our imaginations for potential solutions that meet the users' needs.
Before this phase, the team naturally walks around the room reviewing the walls, covered in the critical path and lots of sticky notes.
We start with "pitch practice" again, comparing to the critical path.
We then have each person individually sketch 10+ user flows and user interfaces. We ask people to include the sources where the customer will come from: Twitter? A blog post? AdWords? Automated suggestions? Drip email? Referral from a friend? Push notification?
Should the product become realized, these sources should eventually be measured in Google Analytics' "Acquisition Channels" report:
We then put those sketches on the wall and begin a silent critique, observing and putting dot stickers on different parts of the flows and user interfaces that we like. This helps visually identify the best ideas.
We then do a group critique, three minutes per idea. The group explains their dots. The author can then add any extra commentary. We don't shoot down any ideas or start winnowing. This voting process helps avoid long discussions and design-by-committee.
Lastly, we do a "super vote" with larger, red dot stickers. The CEO or other product owner will place a single "super vote" on what they think is the best idea. This helps us reflect the reality of how their organization makes decisions and affirm their ultimate authority.
Our experience has been that this phase is mentally exhausting. We recommend ending early and sending people home to re-charge their batteries.
The exercises in this phase force us to stop generating new solutions, converge on the best, and write the test for the prototype.
First, we identify assumptions we're making in the best ideas. We list all assumptions about users' motivations, the business model, our ability to acquire users, and our ability to implement the solution within budget. This helps eliminate some options.
We then look for conflicts in the remaining sticky notes, user flows, and user interfaces: ideas that aim to solve the same problem in different ways. We eliminate solutions that can't be pursued currently.
We then decide whether we're creating one prototype ("best shot") or multiple ("battle royale"). Multiple prototypes are more initial work but may reveal more dead ends and help us dodge more bullets without running follow-on sprints.
We then storyboard each prototype. This is a comic book-style story of our customer moving through the critical path.
Lastly, we create the testing script in Google Docs and put the scoreboard on the wall of the observation room. The script is based on the storyboard and the scoreboard will be used to record the results of the test. This helps us be very explicit about what we're trying to learn.
After another pitch practice to rally the troops, there are no exercises in this phase. It is entirely focused on building the right prototype(s) with just the right amount of fidelity to generate useful test results.
For the entire phase, we ask our clients to write copy text in Google Docs. They write real text, not lorem ipsum, in order to test user understanding and enthusiasm. This text is also useful later for tweets, press, ad copy, landing pages, etc.
While our clients are busy getting communication polished, we are heads-down prototyping. We use different tools depending on the designer and the project.
For web app prototypes, some good options are:
For mobile app prototypes, some good options are:
Don't try to learn these tools during the sprint. Get familiar with them during investment time. During the sprint, use one that you've mastered.
Finally, we interview 5 users to test our understanding of them, their context, and our prototype. This is not a usability test. We begin the conversation as an interview before showing them the prototype.
One of our designers interviews each user. We set them up in an interview room with video and audio streaming to the observation, where the rest of the stakeholders are watching, discussing, and recording answers on the scoreboard.
Good questions are open-ended to allow users to tell stories.
"Could you tell us about a time you donated to a non-profit?"
Don't lead users to an expected answer.
"Would you donate money to a public school if you could?"
Don't close the conversation.
"Have you donated money to an organization within the last week?"
For a new product, it's unlikely that any team will nail it their first sprint. The most likely outcome is that we'll want to run a follow-on sprint starting at Diverge or Converge and test again with a new users.
After one or two sprints, we typically have many assumptions validated, a clear critical path established, and are ready to begin coding a first version to release to a wider audience.
For web apps, we can typically ship a first version in 4-6 weeks. For mobile apps, we can typically ship a beta via HockeyApp in 6-8 weeks and ship to the App Store in 8-10 weeks.
Given those timelines, spending an extra 2-5 days doing a second or even third truncated product design sprint is worth the opportunity cost of spending 4x-10x more time and money to learn we bombed.
Another outcome is that there's no clear user pain, or the business model is murky, or everything we thought we knew was proven wrong. That's an emotional blow, but a success. Time and money were saved.
We end the phase with a plan for moving forward.
Early in a project, we have to decide which platforms we'll use.
Which platform depends on our ideas for solving these users' problems. For example, if they're construction workers on a job site, a mobile or tablet interface might be the best choice.
After considering what's best for users, what's best for us?
In our experience, teams using the Ruby on Rails framework can bring products to market more quickly and with a lower total cost of ownership than other tools, because the framework itself and surrounding community embrace a "convention over configuration" mindset. This means that one Rails app's codebase will look very similar to another Rails app's codebase, and the team will find themselves in familiar technical territory, freeing them up to focus on the product instead of wrestling with the code. There's also strong overlap between the agile and Ruby communities, which means (among things) that Ruby developers tend to write tests, use object-oriented design, and avoid repeated code.
Maybe the greatest compliment we can pay to Rails is that we've made an existential financial commitment to it, betting the future of the company on it in 2005, and we're still here.
In return, we're proud of our contributions to the community, in particular our open source libraries and articles on our blog, GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS.
In addition to Ruby, we use other open source software and web standards such as HTML, CSS, JavaScript, UNIX, Vim, and Postgres because they:
Ruby on Rails comes with features that decrease the burden on the programmer to protect against security attacks such as:
Rails helps us do the right thing with regards to security but we are still required to be diligent, knowledgeable, and test comprehensively. For more information, see the Ruby on Rails Security Guide.
We support Internet Explorer 10.0+ and the latest versions of Firefox, Chrome, and Safari. We do not support Internet Explorer 6, 7, 8, or 9. Those browsers are losing market share, they have security issues, and they are time-consuming to design for, develop for, and support.
On mobile devices, we support iOS Safari 7.1+, Android Browser 4.4+, and the latest version of Chrome for Android.
In limited special cases, user demographics will dictate that supporting an older version of Internet Explorer is required. Those special cases should be identified early on so we can plan for additional time and expense in order to support the version.
"Mobile" refers to the user, not the device.
Everything about how we design a mobile application has to be in the context of that idea. It raises questions like:
We try to start with the most usable platform first. If the device needs the camera, calendar, or address book, a "native" app like an iPhone or iPad app may be the right choice.
For other products, especially content-only products such as text, images, videos, and landing pages, a mobile web app makes sense because:
Our mobile developers' expertise is with the Objective-C, and more recently Swift, programming languages and iOS frameworks such as Cocoa.
We don't take on Titanium or PhoneGap projects because of:
Examples of languages we typically use are:
"Server-side" means code that is run on servers provided by the application hosting company. "Client-side" means code that is run in users' web browsers.
Ruby on Rails, Node.js, and other libraries are frameworks that require developers know the underlying language such as Ruby or JavaScript.
Examples of frameworks we typically use are:
A framework is a library that makes performing a particular task in a programming language easier. Like the framework of a house, it is there when we begin programming and is always there giving the program structure and support.
It can be difficult to switch from one framework to another. The more code that's written specifically for Rails, the harder it will be to switch to Django. That would approach "total rewrite" territory.
For data that must be saved and stored correctly, we use PostgreSQL (we usually refer to it as "Postgres").
It's a 30 year old open source database that is highly respected, well supported by documentation and hosting providers, and used by any developer who knows the SQL standard.
In recent years, a movement called NoSQL has gained popularity. Best translated as "not only SQL", tremendous effort has been made to create different kinds of databases for different use cases, often based off academic or industry research.
Our most frequently used NoSQL database is Redis, which we use for storing transient, high quantity read/write data such as activity feeds, tags, background jobs, sessions, tokens, and counters.
Redis is reliable, open-source, and simple. It offers high performance and reliable predictions of its performance. It can flexibly model different data sets but we typically use it for small data structures, not large images, videos, or text documents.
We typically use Redis to Go to host our production Redis databases.
In contrast with a proprietary license, the source code of an open source program is made available for review, modification and redistribution. The difference between open source licenses is what we can and can't do with the source code.
Open source licenses can be divided in two categories: permissive and copyleft.
Permissive examples include:
A copyleft example is the General Public License (GPL).
Both categories have the purpose of establishing the copyright holder for the software, granting users the right to copy, modify and redistribute it, protecting the copyright holder from any potential guarantees that the software may provide (software is provided as-is), and optionally imposing some restrictions.
Permissive licenses let us modify a program, redistribute it, and even sell it. We can embed or link code with other programs without restriction or explicit permission by the copyright holder.
Copyleft licenses only allow us to link or distribute code with other code that has the same license. It also forces modifications to be released under the same license. Combining anything with the GPL makes it GPL.
Non-copyleft licenses do not enforce derivative works to also be open source.
Some software is released under a dual license: both a permissive and copyleft license. This provides developers who use the dual licensed code to apply the license that better suits their needs.
Most of the software we use has a permissive license:
Your laptop is your sword. Don't go into battle without it. Set up your laptop with this script and these dotfiles.
Laptop is a script to set up a Mac OS X or Linux laptop for Rails development. It should take less than 15 minutes to install.
This sets up compilers, databases, programming languages, package management systems, installers, and other critical programs to our daily programming activities.
'Dotfile' is a generalized term for a UNIX configuration file, typically prefixed with a dot (e.g. .vimrc
) and found in your home directory. Most UNIX programs, including Vim, will load a dotfile during launch.
We recommend using dotfiles to customize your tools and environment to suit your preferences, reduce typing, and get work done. Check them into a git repository for safe-keeping and open-source for the benefit of others.
Use our dotfiles to make pair programming with teammates easier and make each other more productive.
Plain text won't become obsolete. It helps leverage your work and simplifies debugging and testing. The editor should be an extension of your hand; make sure your editor is configurable, extensible, and programmable. -The Pragmatic Programmer
Almost everyone at thoughtbot uses Vim as our text editor.
When we use Vim, we type few characters and avoid the mouse. We're productive and more easily achieve flow.
It's great because:
One of our primary process goals is to make frequent, small releases of our working software.
This section describes how we achieve one week's worth of iteration on a product. It lays out the process we follow and some communication tactics.
Every morning, we get together as a team for 10 minutes at 10 AM.
We say what we did yesterday, what we're going to do today, and if anything is blocking us. We immediately resolve blockers or help the person after standup.
We do this in order to:
We have used JIRA, Pivotal Tracker, Lighthouse, Basecamp, Trajectory, Unfuddle, and other task management systems over the years. The following section details a process using Trello but the overall process remains relatively similar across different systems.
No two products are the same, so flexibility in the product development process is important. Trello responds well to changing the structure of the process "on the fly."
A Trello board is a software equivalent of a physical wall with columns of sticky notes. In Trello terminology, the wall is called a "board." The columns are called "lists." The sticky notes in columns are called "cards."
In the following image, "Current" is an example of a board. "In Progress" is an example of a list. "Confirm Internet Explorer support" is an example of a card.
In any task management system, it's important to have a view into the product development process like this. The Next Up list is the single prioritized list to which the product team refers in order to know what to work on next. It represents one week of work.
A card represents a jobs story, bug fix, engineering task, or general todo.
Cards start out as a simple idea, 1-2 sentences long. As they are pulled through boards, detail is added, explaining why (from a business perspective) we're focusing on it, and maybe notes on suggested implementation (though designers and developers may take or leave it at their discretion; it's supposed to be helpful, not prescriptive).
Once the cards in the Next Up list have been prioritized and vetted, they are ready for design and development. A designer or developer "puts their face on it" by assigning it to themselves and pulling it into the In Progress list.
The cards in the In Progress list are actively being designed or developed. Etiquette is that you should never have your face on more than two cards at a time. Work is done in a feature branch.
When a designer or developer creates a pull request for their feature branch, they move the card to the Code Review list. Any reviewers "put their face on it" while reviewing.
There is no bottleneck for merging into master: everyone can do it.
The cards in the Testing on Staging (or Testing on Ad Hoc build for iPhone apps) list are deployed to staging (or distributed via HockeyApp for iPhone apps). The card creator and a designer review it for accuracy and user experience.
There is no bottleneck for deploying to staging: everyone can do it.
The cards in the Ready for Production list include cards that have been accepted on staging and are ready to be deployed (but not necessarily rolled out).
There is no bottleneck for releasing to production: everyone can do it.
The cards in the Live (Week of [date]) lists have been released. Each week has its own Live list so we can follow what got released when.
Once a week, usually on Monday, everyone meets in-person or via video conference. This replaces Monday's daily standup and is an opportunity for the entire team to discuss achievements, hurdles, and concerns with the goal of everyone leaving excited and empowered for the week of work to come.
The advisor runs this meeting.
Notes from a retro may look something like this:
Joel
- last week
- felt like it went by extremely fast
- first couple days, thinking through the project, understanding
- didn't feel like we got much implemented in code
- feel great about knowing what we're building
- this week
- feeling confident
Ryan
- last week
- fast-paced with understanding, overwhelmed with the complexity
- towards the end of the week with prototyping and iteration, it helped a lot
- this week
- feel much better than start of last week
- brainstorming + prototype helps a lot
Yadid
- last week
- flew by, felt like it didn't happen
- progressed a lot
- defining the interaction was really important
- confident moving forward with what we decided upon
- this week
- time is worrying
- user study, potentially risky
Concerns
- timeline - it's a tight project (JQ, RC, YA)
- concerned with choice of technology with vanilla Rails (JQ, YA)
- lots of state involved
- concerns around interaction and not specifically the visual design (RC)
- testing (potentially won't change outcome) (YA)
- need a staging server (JQ)
- don't want to connect to real API
- in dev+test we've created a fake API that we're connecting to
- can't do that on Heroku
Addressing concerns
- Yadid to set up a staging server for the app to interact with
- Ryan to do a quick run-through with Yadid re: interactions
- Josh to look into Omar rotating on
The stage of the product should guide the planning meeting. For example:
Tell customer stories. Do people love this product? Show numbers. Are more people using the product this week than last? Are the same people using the product more this week than last?
In all stages, we should be asking:
Based on the answers to these questions, we record our plans in the task management system:
The task management system is the canonical repository for plans.
When things are only said on the phone, in person, in emails that don't include the whole group, or in one-on-one chats, information gets lost, forgotten, or misinterpreted. The problems expand when someone joins or leaves the project.
During this meeting, seek discussion with, not instruction from, clients. We can't talk about solutions until we identify the underlying problems.
We've been called "aggressive" with our approach cutting features, budgets, and schedules. We say "no" a lot. It's hard to say "no." "No" is usually not well-received. There's a reason someone requested the feature.
We have to battle sometimes in the face of "yes". We do so armed with knowledge of the history of software success and failure: in 2004, only 34% of software projects were considered successes. The good news is that was 100% better than the stats in 1994. "The primary reason is the projects have gotten a lot smaller."
Few software projects fail because they aren't complicated enough. Saying "no" means keeping the software we're building as simple as possible. Every line of code we write is an asset and a liability.
Simple software, once launched, is better suited to meeting the demands of customers. Complex software, if it ever even launches, is not as able to respond to customer demands quickly.
Tools like Slack, GitHub, and Trello have made remote work much easier over the years, and we work remotely every day within thoughtbot across offices.
Remote consulting work is possible, but raises the degree of difficulty. One of the few requirements of Extreme Programming is that the customer is always available.
Ideally, that means face-to-face, on-site. We've set up our offices so that our clients work at the same cluster of desks as our teams. Nothing beats in-person communication.
An ideal consulting project for us is one where a member of the client team is willing to work at our office Monday-Thursday for the duration of the project. Failing that, we want to find out during the sales process how available they will be on Slack, GitHub, and Trello.
If it seems like they won't be available very often, we should seriously consider declining the project.
Remote work is when the client and the team are in different locations, either the team is in another thoughtbot office from the client or team members work from a different location for the duration of the project.
Meet in person
At the beginning of a remote engagement, when possible, everyone should meet in person for at least one work week. This is useful in order to get to know the team members better and to develop relationships, which will make it easier to communicate through asynchronous channels in the future.
If possible, it is beneficial to meet in person again throughout the project.
Well-defined roles and workflows
At the beginning of the project define who handles which role, and how to communicate amongst the team. For example, decide whether standups will be done via group chat messages or via voice or video call.
If part of the team is remote, the whole team should work as if it were remote. We should over-communicate. Major decisions regarding the project must be documented online in a medium everyone is aware of and able to contribute to. This means that all project-related communication should be done in asynchronous channels that we already use, such as GitHub, Trello, Basecamp, and Slack. The only workflow difference using these tools when doing remote work is that we communicate all important information asynchronously, so that everyone stays informed.
In person communication during a project usually includes frequent updates on current work and various social interactions. The chat room should be the space where we communicate, so that no one feels left out, especially team members who work remotely.
Team members should also be conscious that asynchronous communication means that sometimes the other person is not immediately available to respond, and not expect them to. Furthermore, online communication lacks the non-verbal visual cues such as voice tone and inflection, facial expressions, and body language. We should be more careful in the language that we choose to use. A good reference is our existing code review guide.
Feeling isolated
When working remotely, especially when alone, it is easy to forget how it feels to be immersed in team camaraderie. Video conferencing helps alleviate this feeling as well as occasional visits to the office, or working from a co-working space.
Work hours
We should try to have 4-6 hours (with consideration of time differences) in the work day that overlap between locations to allow for synchronous communication.
For some people, it is sometimes difficult to disengage from work when working at home. Also, flexible hours means that sometime one may work non-traditional hours in the day. We should be conscious to keep to a sustainable pace and take a break away from work.
Tools
Good tools for remote pair programming are:
A single Trello board with a few lists as described above works well for most early-stage teams and products. As they grow, however, more organizational tools may be necessary. For example, we might want to first add lists to the "Current" board such as:
Later on, those lists might be better organized as entire boards themselves. Separated as boards, it's easier to evaluate the relative value of addressing each related thing. Separate boards also keep the "Current" board clean and the product team focused on the week at hand.
Each of those boards can be organized as the team sees fit for the stage of the product and the team's communication needs.
On the "Bugs" board, we've sometimes used labels to describe relative criticality of the bug. If a bug is labeled Critical, then it is pulled immediately into Next Up on the "Current" board. If the bug is not critical, it stays in Bugs until the next weekly retrospective. A bug has steps to reproduce the bug and optionally a screenshot or screencast.
The cards on the "Product Design" board are typically the result of sketching user flows, usability tests, other user research, or the designer's feel for visual design improvements.
The cards on the "Engineering" board are refactorings and other engineering tasks necessary to reduce bugs or improve the user experience. "Response time" is a primary user experience goal on every app. If an engineering task is labeled Critical, then it is pulled immediately to Next Up. If the task is not critical, it stays in Engineering until the next weekly retrospective.
This may surprise you, but most of our designers use Vim as their primary text editor. This isn't the only characteristic that has become distinctly thoughtbot.
Many of our projects are frequently undergoing rapid change. Without classic Photoshop comps or requirements documents, how do our designers fit into the process?
Just like during the product design sprint, our designers are often sketching interfaces before implementing them. Also like the sprint, anyone on the team is encouraged to sketch at any time.
We have many Moleskine squared, soft, pocket-sized notebooks around the offices. Take one. The pocket size encourages the habit of getting ideas onto paper whenever and wherever they hit us. The pocket size also forces design constraints and mobile-first thinking.
The designer will then refine the sketches into HTML and CSS wireframes. HTML and CSS wireframes are built using Bourbon and Neat in the browser so the team can understand the core experience as fast as possible. It also allows developers to start implementing features within the wireframes.
It is crucial to keep the design of the application ahead of the development. Focus should be placed on wireframing usability, user experience, and flows.
We find it important to keep the design and development cycle adequately tight. We do not wireframe one month out because as we approach certain areas of the product, we often decide to cut or change features.
Those changes are an expected part of the iterative process and feedback loop between the client, the thoughtbot team, and users. It would be wasteful to spend time wireframing features that never get built or building features that won't be used.
An interface is a place where two things meet: the human and the computer. The computer has functions it can perform. The human needs inputs and outputs to take advantage of those functions. The interface is the arrangement of inputs and outputs that enable people to apply the computer's functions to create outcomes they want. - Ryan Singer
In the context of our software, the user interface is the individual views that provide for goal completion.
We evaluate interfaces on the following criteria:
We put the users goals first. No one is using our software exclusively because of how beautiful it is. There's a reason they sought our solution out. Making that outcome easily attainable and desirable is our highest priority.
We make software easy to comprehend. It's not enough to be functional, users must know capabilities exist and be able to anticipate how the software is going to react to their inputs. Our software should be as intuitive as possible.
We remain consistent with platform guidelines. Interfaces look and feel best when in congruence with their context, rather than being strictly branded across all platforms. We prefer common patterns when designing.
We retain consistency. Usable interfaces work as expected across the entire application.
Interaction gives users the ability to change the canvas, to directly manipulate. Designing those interactions is what makes our software come to life. Interactions should provide affordance — animation, for examples, can be used as a powerful metaphor for helping a user understand an interface. Interactions help guide a user from the beginning of a task through it's completion.
Designers guide these interactions from prototype to implementation. For web applications we start in the browser. For iOS, we use QuartzComposer. For review, we use gifs to demonstrate interactions.
We refer to an application's visual design exclusively as its style. We use the universal design principles to communicate and bring order to those ideas in our applications.
Those fundamentals include, among others:
Successful visual designs typically don't draw attention to themselves. The content will be front-and-center. The workflows through the site will be obvious. Resist the temptation to aim for a design that is "memorable" or a design that "pops."
Successful designs are usable. Consider Google's visual design:
Everything's on a grid. The "Search Mail" and "Compose Mail" buttons are emphasized over other calls to action with color. The unread messages are emphasized over other messages with a bold font weight.
Everything's on a grid. There is lots of whitespace (especially with AdBlock). Search interface is consistent with Gmail. Search button is emphasized with color.
Grids. Whitespace. Consistent search box, search filters, search results.
Grids. Whitespace. Emphasis on searching and writing a review.
We say "visual design" instead of "graphic design" because typically graphics aren't called for in our applications. Instead, we rely on these principles and excellent typography, using high-quality typefaces from Typekit and typography.com.
Running user interviews and usability tests early and often is critical to product success. We even test sketches to get a feel for flows and mental models. The earlier the stage, the more we're testing the problem/solution fit and gathering research on potential users. The later the stage, the more we're testing actual usability of the product.
User interviews and usability tests are the most effective way to test a product viability and usability. By continually testing it verifies that the product and team are focused on solving people's real problems and creating a great user experience for the product. We've found that having a testing plan early is the best way to consistently run user interviews and usability tests during the project. A starting point for the project's interviews and testing should be biweekly. This sets the expectation that interviews and testing are needed and the team should discuss if that plan will work for their individual project. Testing should always accommodate the users and project needs.
We're testing the software, not you.
Usability is the measure of how easy it is for a user to reach an outcome.
We think about usability testing similarly to test-driven development: writing falsifiable outcomes for users. Our outcomes are written in the form of a script that's written in the same way as a jobs story.
When I arrive at work I want to review the team's status updates so I can help any blocked teamates.
Once the script is written, we find testers.
The most representative candidates are going to be sourced from our existing userbase. Send out a tweet, add a banner to our site, or add a link to our newsletter. Even pre-launched projects have a mailing list to use.
When we have trouble sourcing or aren't interested in existing users, craigslist can be effective to find candidates. Our office manager puts a posting on craigslist, schedules them to come into our office, and pays them $30 for their time after the test.
We have a simple template for finding people on craigslist.
After the tester has arrived, we introduce ourselves and explain the process. There's no need to be strictly formal, we want them to be at ease. To have a relaxed user test, it's important to remind the user of a few things:
If we are filming them, we ask them to sign a simple consent form.
Once underway, we ask them to say out loud what they're thinking as they're using the software, which can feel unnatural but is important. While running the session, the only reasons to speak are to get them to talk out loud again, ask questions, and provide them with outcomes to achieve.
We should avoid leading questions such as "Was this task difficult for you?" but should ask follow-up questions. For example, if someone says "That's awesome!", we shouldn't silently pat ourselves on the back. We should say "Why is it awesome for you?" We are seeking understanding.
After the session, we look back at our notes to identify the top handful of problems and fix them before the next round of usability tests. If there's a problem revealed with the navigation, there's a temptation to totally re-do it. We try to resist that urge and come up with less drastic changes.
The majority of our development practices were first detailed in Kent Beck's classic Extreme Programming Explained: Embrace Change and in Gerald Weinberg's The Psychology of Computer Programming. We've tried its practices and found that using most of the practices most of the time improves the quality of our work and happiness of our team.
We always use source code control. It's like a time machine. We can work in parallel universes of our source code, experimenting without fear of losing work. Roll back if something goes wrong.
Git is an open source source code control system written by Linus Torvalds. It's fast and great for working in branches.
We use GitHub for hosting our git repositories.
We write code in a consistent style that emphasizes cleanliness and team communication.
High level guidelines:
Code that is written by two people who sit next to each other at the same computer is pair-programmed code. That code is considered high quality and should result in cost savings due to less maintenance.
In the long run, this style of development saves money because fewer bugs are written and therefore do not need to be fixed later.
An indication that pairing is beneficial and should be done more often is the following example:
When you are writing an important piece of code, don't you want another person to look it over before it goes into production?
While we don't pair program 100% of the time, we recognize the difficulty in acting as a team when we work at a distance from each other. There is no better collaboration between designers, developers, or between designers and developers than at the keyboard.
Test-Driven Development (TDD) is perhaps the most important Extreme Programming (XP) rule that we practice.
Business benefits of TDD:
Code benefits of TDD:
Process benefits of TDD:
At a high level, how to test is very simple:
For more specifics, we recommend our Test-Driven Rails workshop, which we run about once a month. It goes into incredible detail about the TDD workflow specifically for Ruby on Rails developers.
Acceptance tests are code created from jobs stories. They look like this. This code is run against the application. When executed for the first time, the test will fail. The developer writes application code until the test passes.
When the test passes, the developer commits the code into version control with a message such as:
Guest creates pledge
The code is then run on the Continuous Integration server to make sure the acceptance test still passes in an environment that matches the production environment.
Meanwhile, the code is pushed to the staging environment and the developer and customer representative smoke test it in the browser.
When the acceptance test is green for the CI server and you and any other designers, developers, or clients are satisfied that the jobs story is complete on staging, the feature can be deployed to production at will. This can result in features being pushed to production very frequently, and therefore more value is being delivered to customers sooner.
The third step of the "red, green, refactor" step is refactoring, the process of improving the design of existing code without altering its external behavior. It's a critical step in the process, but often overlooked. We're so passionate about refactoring, we wrote an entire book on it, Ruby Science.
Here's the flow. Read our git protocol for the git commands.
Test-Driven Development moves code failure earlier in the development process. It's better to have a failing test on your development machine than in production. It also allows you to have tighter feedback cycles.
Code reviews that happen right before code goes into master offer similar benefits:
Martin Fowler has an extensive description of Continuous Integration. The basics are:
This helps ensure there's nothing specific to the developer's machine making the tests pass. The code in version control needs to run cleanly in production later so before the code is allowed to be deployed to production, it is run on a CI server or service.
When a build fails, we get alerts in Slack and via email. Click the alert and we see a backtrace that gives us a hint of how to "fix the build."
When we write the fix and commit to version control again, we'll get a "passing build" alert in Slack and via email. Click the alert and we see the passing build.
Green is good.
A solid test suite is an absolute requirement for a web application in our opinion. However, one major problem with test suites is that they get slow as they get large.
CI can ease the pain by distributing the test runs in parallel. We've had 45 minute test suites cut down to 2 minutes using this technique.
We've used CruiseControl, Integrity, Hudson (now called Jenkins), and other CI libraries that we manage ourselves. This resulted in many hours of expensive attention.
We use Travis CI Free for open source projects. We use Travis CI Pro for private repositories because of its consistent UI, simple configuration and its close integration with GitHub.
CI test runs are triggered by GitHub post-receive hooks. The hooks we have on most of our GitHub repos are:
We live in a magical modern era where many problems have already been solved for us. We focus on the client's product as much as possible and outsource operations as much as possible to external services.
This saves time and money. We can get started using those services in minutes. Our clients pay a service tens or hundreds of dollars per month instead of paying developers thousands or tens of thousands.
We often create a Google spreadsheet listing the monthly cost, description, and credentials of each of our clients' external services. It includes line items like GitHub, Heroku, SendGrid, New Relic, and Airbrake.
We have found that a short checklist is valuable when setting up a new production environment or preparing for a launch:
api.example.com
)? Even if the same app, this gives us architectural flexibility in the future.Gemfile
? See how to set it up.Use Domainr to see what's available.
Use DNSimple to buy and maintain domain names. If a client already has a domain registered elsewhere, like GoDaddy, DNSimple provides a transfer service that makes it easy to switch.
We like it for its simplicity. It also has templates we most often need:
Follow the Custom Domains tutorial to set up root and subdomains on Heroku.
Buy a wildcard certificate from DNSimple. The wildcard (*
) lets you use the same certificate on www.
, staging.
, api.
, and any other future subdomains.
Follow these steps for adding a DNSimple SSL certificate to Heroku.
SSL and DNS are tightly coupled. If we're doing any work with SSL, we need to make sure we have access to make DNS changes, like adding a CNAME record. If we're working with a client who has a department that handles DNS, schedule time during off-peak hours to pair program with the DNS person to make sure everything goes well. We can accidentally take down a site that is all SSL if this work isn't done methodically.
We use Heroku. It's a platform built on Amazon's cloud infrastructure. It is simple to use when our app is just a toy and is built to scale up for high concurrency or high sustained load.
Like Rails, Heroku uses conventions to make decisions for us that are unnecessary for us to make. Some things like web servers and app servers are solved problems. They act as our outsourced operations team. The amount of time we can focus on the product instead of solved problems is worth the premium over bare-bones Amazon Web Services.
The cloud promises lower operating costs, especially at the beginning when capacity can be lower. Forget about sunk costs of expensive servers.
The cloud and the services it enables will empower our clients' businesses to start and operate in a manner that has never been possible before without significant upfront investment.
If we offer file uploads for features like user avatars, we upload them to Amazon S3.
We also serve our images, CSS, and JavaScript assets from a CDN such as Fastly.
We use NewRelic (Free-$100s/month) to monitor performance of production applications.
Debugging performance might be the best part of a developer's job. There's a clear, numeric problem. When we fix it, that number improves. We can say things like "We made this 175% better."
There's many established techniques for fixing performance problems. A number of them come "for free" with Rails + Heroku:
A number of them require developer thought:
Page caching is the heaviest handed technique we have, but if we can cache an entire page and push it into a CDN, that will be the fastest option.
Most applications write useful debugging information to logs. On Heroku, these go to standard output by default and are eventually discarded.
We typically use Logentries to accept logs from Heroku and other sources. Once sent to Logentries, you can search previous logs and set up alerts for errors outside the Rails stack, such as out of memory errors.
If you're adding Logentries to a client project, the best solution is to have the client set up their own Logentries account and add the relevant thoughtbot members. If the client doesn't want to set it up or there's too much red tape, you can create a new project on the thoughtbot Logentries account and then delete it once our engagement has ended.
If you don't have a login for the thoughtbot Logentries account yet, ask somebody to invite you. Don't use the Heroku Logentries addon, as it will automatically create a number of alerts and send them to every admin on our Heroku organization.
If you'd like to set up Logentries for a Heroku application, follow the instructions for setting up a syslog drain.
We use Airbrake Bug Tracker (Free-$25/month).
We use SendGrid (Free-$400/month) to have our application deliver email to users, known as transactional email.
Examples of transactional email are:
We use SendGrid directly, not via the Heroku add-on, in order to avoid being lumped under the same IP group as others on Heroku (who might be misbehaving).
For collecting payments from users via credit or debit card, we use Stripe. It is a payment gateway and merchant account. We also use it for recurring billing.
Charges for Stripe will vary depending on usage. Successful charges are 2.9% + 30 cents. There are no setup fees, monthly fees, or card storage fees.
For sending money to users' bank accounts via ACH, we use Balanced.
"If you can not measure it, you can not improve it." -- Lord Kelvin
The difficult part of measuring is deciding what to track.
Dave McClure's AARRR framework provides a high-level overview of important metrics. We then use tactics such as event tracking to instrument those metrics.
The AARR framework is:
For an early stage product, we work to improve them in this order:
In order to analyze metrics later, we need to instrument our app to log the right metrics. The primary type of instrumentation we care about is called "event tracking."
Use Segment.io to capture events whenever possible. It is basically the adapter pattern for analytics services.
Segment.io lets us drop one JS library into our web apps, or one Ruby library into our server-side framework, or one iOS SDK into our mobile apps. Then, we can toggle on and off different services such as Google Analytics, Mixpanel, Intercom, Olark, Localytics, and others.
Segment.io's API endpoint is hosted on Amazon CloudFront. So, it has extremely good uptime and you can have Segment pipe the CloudFront logs for your apps to your own S3 bucket, giving you access to your raw logs.
When Segment.io does not support a backend service, we can either use the service directly or contribute to the appropriate Segment.io open source libraries.
The hardest part of event tracking is choosing the granularity of the events. It's costly to reconstruct historical measurements and missed knowledge can kill an early-stage product. So:
Some typical events we'll want to track:
Use properties on events liberally. Typically include:
Business analytics do not need to be realtime and tracking these data should not slow down the user experience. So, we background these tasks whenever possible using whatever backgrounding system makes sense for our platform. Examples include Delayed Job and IronWorker.
We work on a lot of products that have a monthly or yearly subscription business model. There are some classic metrics we know we want to track for these products, such as:
Since we use Stripe for payments, we've found Baremetrics is the fastest and easiest way to track these metrics.
If our clients want to raise money from investors, the following numbers are generally considered investment-ready:
Churn is particularly critical when fundraising. Small changes in churn can drastically improve valuation.
Calculating CAC is a manual spreadsheet exercise. It requires adding employee overhead costs and direct marketing costs together, then dividing by the number of new customers for that month.
For Upcase, we can rely on our bookkeeper, Supporting Strategies to provide us with those numbers, and make adjustments for which vendors such as Google (AdWords), Twitter, or AdRoll fall under the direct marketing accounting class.
Someone can tell us in a usability test when they're confused by a page or when they're frustrated by upsells during the checkout process. They probably can't tell us whether one set of copy or another is more likely to make them feel an affinity with our landing page, pull out their wallet, and plunk down cash.
So, we A/B test landing pages and payment flows.
We don't A/B test price. Users talk to each other and it can make customer support difficult. We test the price off-line via customer interviews instead.
Software is soft. It's always changing. Hopefully, we're always learning from our changes.
A cool way to manage changes is via feature flags. Using a tool like Rollout, we can "flag" certain features as only ready for parts of our user base; i.e. just the development team, or just the founder's friends, or 10% of all users, etc.
That way, we can see how users respond to the feature without rolling it out to everyone. We can pair this technique with A/B testing to compare how users respond to different features.
We're designers and developers. We want to design and develop software. Before we can do that, we need clients to hire us. The following section details how our sales process works and answers commonly asked questions by potential clients.
The overall process is:
Our leads often come from referrals from clients and Google searches.
We track each lead on a Trello card in the "Contacted" list on our "Sales" board:
We manually create cards for personal introductions. Our new project form automatically creates cards for each submission. Zapier automatically creates cards for each voicemail we receive into our main phone line.
The Managing Director will get assigned to the card for the incoming lead but anyone can take responsibility for that lead. The person responsible qualifies or disqualifies the lead, often with a quick intro email or phone call with the potential client. Before they do that they should add themselves as a member on the Trello card.
We prefer to pair on sales calls, having at least one designer and one developer on the call. This enables us to get multiple opinions on how good or bad of a fit the client and project might be for us, it gives us the ability to answer both design and development questions to the best of our ability, and it allows us to train and improve our process during sales calls.
Our goal is to begin thinking about the client's product and working as a team to plan it even before we officially start working together. Some example questions to ask:
We distribute the sales process throughout the team. Potential clients should be able to talk to the people they'll work with. We should be able to handle spikes in incoming leads that make it unreasonable for a managing director to respond in a timely fashion.
We use an internal app to manage our schedule and availability. We don't track time, but we do plan in week increments.
If the client asks us to sign an NDA, we respond with:
Are you willing to chat without signing an NDA?
We've worked with hundreds of clients. We talk to hundreds of potential clients each year. It's inevitable that we hear similar ideas.
If the NDA is important to the client, ask them to tell us enough about the business to evaluate whether there's a conflict with our existing or past clients. If we determine there's no conflict, the project is a good fit, and the NDA is mutual, we sign it. If their NDA is not mutual, we use our NDA.
We offer some combination of designers, Ruby developers, and iOS developers. An advisor assists the team for a few hours a week. Everyone is T-shaped, deep in some area of expertise with the ability to collaborate across disciplines.
We are people, not "resources", and avoid calling each other such because we understand we are working with each other as people.
The designer is responsible for designing interactions between users and the product. They write user interface code.
The developers make it work. They write the code that makes the app "smart." They aim to make the product error-free. They monitor performance because speed is a feature of every application.
Developers keep it running. They make architectural decisions and interact with modern-day hosting companies like Heroku, whose employees double as our outsourced operations team.
The developers also implement. They write and maintain HTML, CSS, JavaScript, Ruby, SQL, and lots of other code. They set and meet development standards, keep the Continuous Integration build passing, and review each others' code.
The advisor adds an impartial perspective. They run weekly meetings so that there is consistency in week-to-week communication. They keep an eye on the high-level goals of the project, which should be easier for them because they are not in the weeds of the project day-to-day. They express enthusiasm when the team is in a groove and help problem-solve when things get off track.
When appropriate, they should work with the client to either reduce or increase team size to correctly serve the project.
The advisor is not a project manager. The rest of the team does not report to them. The advisor is also not a technical or design lead. Advisors need not be Managing Directors or CXOs, but typically are due to flexibility in schedule. Anyone at thoughtbot should be able to advise a project. If the primary salesperson is not also the advisor, there should be a smooth hand-off from one to the other.
While each person plays a role, a team needs to be a team.
Everyone takes responsibility every day for delivering high quality work, for staying true to the vision for the product, for communicating their schedule and intentions, for making hard decisions, for delegating to others when they don't have the time or skill to accomplish a task, for keeping team morale up, and for being consistent.
Some consulting relationships start with a requirements document or RFP ("Request For Proposal"). The requirements are often extremely detailed.
The probability of this document containing the optimum feature set is extremely low. The right features are better learned through user interviews, prototyping, releasing actual software, and getting feedback from real users.
Based on that document, clients expect consultants in the industry to submit an exact timeframe and bid. This contract style sets the client and consultant working against each other right from day one. Instead of focusing on designing the product experience or evaluating what assumptions were wrong, they spend time negotiating about what was meant in a document written a long time ago or focusing on arbitrary deadlines. But it's worse than negotiating; it's retroactively discussing something that no one remembers the same way.
As you might have guessed, we don't do fixed-bid, fixed-feature-set proposals.
We do need to know clients' budgets. This is often uncomfortable for them but their budget helps determines what scope is possible. It saves time. If they don't know their budget, we discuss different options.
We talk about breaking product rollout into stages and try to improve the product's chances of success at each stage by:
We price projects at a per person, per week rate. We consult 4 days per week. We use the same rate for designers and developers. The work required for each week dictates which skills are needed. The number of people needed determines the cost and how much gets done.
During the process of explaining our billing, we sometimes tell potential clients "time and materials" is the same as hiring an employee for their annual time except there's less risk to them because:
We don't provide itemized invoices to clients showing individual pieces of work that were done. Clients always know what is happening via access to the project management system (Trello), chat room (Slack), version control system (GitHub), and ongoing communication with our teammates.
Examples of typical projects for us are:
On many occasions we've done projects for clients who have a budget enough for a "Version 1" product. They followed that up with a round of beta invites, time spent learning in the market, a round of funding, or a few months of revenue. They come back for another round of design and development with a more informed sense of where the product needs to go.
The feature set of a product is not necessarily indicative of the length of time required to develop it. Sometimes to get to a very simple product, we have to iterate on it many times. We love working with clients who understand that a great product takes as long as it takes.
In return, we understand that we can never abuse a client's trust in us. We should be maximizing our productivity in order to provide them the most value for our time. Things like our "Research" Trello board, "Code" internal chat room, and shared dotfiles ensure we have a highest common denominator set of tools and techniques ready when the situation arises again.
We store contracts in Dropbox and have a series of folders for pending, current, past, and lost clients.
The consulting proposal and contract contains:
We use Harvest to invoice our clients at the end of each week.
It sends recurring invoices so we don't forget to bill people at the end of the week. It automatically sends late payment notifications, limiting awkward conversations about the bill.
Clients can pay their invoice via check or wire transfer.
We track who at thoughtbot should receive a commission in the project notes field of the project's profile in Harvest. We often split the 5% commission among two or three people who were involved in the sales process.
We are not the permanent team solution for our clients. They often want to know:
We tell them:
We've met our future teammates via:
We met Josh because he submitted excellent patches to Clearance. He was in Michigan at the time. Due in part to their open source work, we've hired great people as far away as India and Thailand. We moved them to the cities where we have offices.
Many of us are regulars at Ruby, JavaScript, Vim, and Redis user groups. We met Ben, Joel, and Mason at the Boston Ruby Group.
A nice thing about those meetings are that they happen naturally. We're not trolling GitHub looking for people or fishing for talent at user groups and conferences. We're there, anyway. If we never hired again, we'd still be writing and using open source. We'd be members of mailing lists and going to events.
We know what we'll get when we hire in the above ways. We know their personality and energy level from the user group. We know their coding style from their open source work. We know they'll take initiative because they voluntarily contributed to the community.
We met Jessie and Laila via Dev Bootcamp. They went through apprentice.io, as did Adarsh, Draper, Edwin, Diana, Melissa, Joël, Lisa, Lydia, Rich, Christian, and Tony.
We've also had great luck finding designers on Authentic Jobs and iOS developers on Stack Overflow Careers.
We track each candidate's progress through the interview process on a Trello card on our "Hiring" board:
We manually create cards for personal introductions. Our new teammate form automatically creates cards for each submission.
Our CEO, Chad, leads the hiring process. He ensures that everyone gets a response and he speaks with everyone before they are hired.
Anyone can do an initial review of the candidate's application. In particular, they review the candidate's code sample or portfolio. If necessary, they may ask someone else (like a designer or iOS developer) for another pair of eyes on the code or portfolio.
We either send them a rejection or an email based on this template, moving the Trello card to "Non-Technical Interview".
Chad will pull the managing director, designers, or developers into subsequent discussions, putting their faces on the Trello cards to ensure we always know who is responsible.
We have standard questions for iOS developers, Rails developers, and designers for the technical interview. We don't use puzzles or code challenges. Instead, we prefer reviewing actual work the candidate has done, and talking to them about design process, architecting systems, and writing code; the same thing we do for work every day.
The final step for candidates is to visit us for a day. We pay for their flights and three nights of hotels (rest up Thursday night, work with us on Friday, enjoy Friday night, explore the city Saturday, fly home Sunday).
On that day, developers pair program with one of our developers in the morning and another in the afternoon.
Designers pair in the morning and work on a small product design project throughout the day and then present at 4pm. It primarily involves sketching and working with one or two thoughtbot designers.
We do the interviews this way because there's no substitute for seeing someone actually do the work and interacting with the team. We also want candidates to experience what the company is like for themselves.
Aside from technical skill, during the entire interview process, we look for character strengths like enthusiasm (invigorates others), focus (pays attention, resists distractions, remembers directions), composure (remains calm when critiqued, doesn't interrupt), gratitude (shows appreciation), curiosity (eager to explore, asks questions to understand, actively listens), optimism (gets over frustrations quickly), grit (finishes what he or she starts, doesn't get blocked), emotional intelligence (demonstrates respect for others' feelings, knows when and how to include others), humor (likes to laugh, makes others smile), and appreciation of beauty (notices and appreciates beauty and excellence).
To be hired, the candidate must get a unanimous "yes" from the existing teammates with whom they interacted.
We use RightSignature ($14-$49/month) to send the offer and get them signed without the "print and scan" process on either end.
Offers are reviewed and approved by at least one member of the C-level executive team before being sent. C-level executives and Managing Directors can execute offers on behalf of thoughtbot.
When the offer is accepted, we run a custom onboarding script which we wrote. It creates the teammate's email address, gives them access to systems like GitHub and Slack, sends them their Employment Agreement, notifies Accounting, sends a welcome email to the teammate, and creates a todo list for the hiring manager for any remaining manual items that we haven't been able to automate.
We assign a pair to new team members who acts as a guide on their first day. The pair helps them set up their machine, purchase any required software, and walk them through one turn of the development cycle by getting their profile added to our website. The pair also makes them feel comfortable, answers questions they may have, or points them to the person who can answer their questions.
We are entirely bootstrapped, with no outside investors, and no debt. We are paid for consulting only four days each week.
Sustainability of the company is very important to us. We want to bring great people on at reasonable salaries and reward them as aggressively as possible for actual performance.
We may never be able to compete dollar for dollar with other tech companies but we can compete on being a great place to work, with lots of opportunities to learn, and the freedom to define and execute on our own projects.
In addition to salary, everyone receives commission on any new work they help bring to the company, and quarterly profit sharing bonuses.
Salary increases are the natural result of improvement, and occur company-wide on a yearly basis. Our manager may increase our salary in a way that is compatible with the company's finances and individually appropriate to us based on things we've done, such as:
Our salary increase may also include adjustments based on market conditions and cost of living increases.
It's important that our manager explains why a raise is being given and what, if anything, could be done to receive a higher raise next time. We don't get raises for "just showing up."
In order to continually improve ourselves and the company, all year round on every project we're on, we receive regular feedback from clients, managers, and teammates. We additionally have formal quarterly reviews.
During onboarding, a "Quarterly Review" calendar event is created, set to recur once every 3 months, starting from the hire date.
Ahead of the quarterly review, our manager collects anonymous feedback from everyone we've worked with in the quarter, and everyone in our office. The team feedback is shared with us before the review.
The agenda for quarterly reviews is roughly:
The results of the quarterly review are recorded and influence future compensation increases.
Running a software-based business requires more than beautiful code or a popular product. Managing cash flow and taxes can feel unimportant or difficult but getting them right is as vital to our success as product design.
Fortunately, many services exist which make things like bookkeeping, receipts, signatures, and invoicing much easier.
Some principles have helped us streamline our operations:
Every full-time employee gets an American Express corporate card for business expenses. We've hired trustworthy people.  It saves time and treats people like adults.
We buy things. The IRS appreciates it when we track those purchases. So do we, in order to know whether we're profitable.
In the US, we use Tallie to send all receipts (meals, travel, books, computers) to our accountant. In Stockholm, we use Shoeboxed. - Instructions on How to Use Tallie. - There are also handy iPhone and Android Shoeboxed apps which take photos of receipts and send them on their way.
We use Gmail for our email.
We use Google Calendar for our calendars.
We use Google Docs for our editable documents.
We prefer Google Docs because they are:
These tools are not well-suited for large documents or complicated spreadsheets, which is great.
We write code and are biased toward minimal documentation and upfront specs so we shouldn't be writing long documents.
For cases where we are writing large spreadsheets, we find it's faster to snap together a small app to do the job. This is a good time to ask if such complicated analysis is really necessary.
When documents are mostly similar with slight variations (like contracts), we create templates using Pages and export to PDF for external use.
We over-communicate with clients in-person and online to avoid having scheduled meetings. Every problem arises from poor communication.
When we need to meet for a discussion, we aim for 30 minutes spent in-person.
When working remotely, Google Hangouts are indispensable as the "next best thing". They are easy to set up, sharable by URL, and let us get a look at whoever we're talking to.
Screen-sharing is also very easy, when necessary. We have used Hangouts for client meetings, candidate interviews, and company meetings.
We use conference lines that are part of our VoIP system, provided by OnSip, for voice conferencing.
Supporting Strategies provides us with an outsourced bookkeeper, controller, and tax accountant/CPA. They provide us with a hosted QuickBooks install that we can access via Remote Desktop.
We use Earth Class Mail to receive all paper mail for our offices. This service automatically opens and scans all paper mail and sends it as a PDF email attachment, which we file in Dropbox. Earth Class Mail also automatically detects checks and deposits them into our bank account.
We interact with them mostly through email. It's very efficient and we pay for what we use, which is great for scaling.
Our accountants review Harvest for new invoices and new payments on a daily basis and input them into QuickBooks. If any checks or wire transfers have come in, they are entered into Harvest and QuickBooks. Harvest sends payment received email notifications to both clients and the management team.
All our receipts go to them automatically.
Our CPA is excellent and provides these services for us:
In Sweden we are assisted by Radius for company, accounting, tax, and human resources.
Our law firm is Gesmer Updegrove LLP. They are able to provide us with legal support for most everything we need, which is most commonly client, real estate, and company/stock matters. We also engage Costa & Riccio LLP for US immigration matters.
In Sweden we are assisted by Newcomers for immigration and relocation matters.
We've learned a ton from blog posts, tweets, and newsletters from others in the community. We should be giving back when we have something to share.
Our blog is called GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS.
When you want to write a post, write its headline as a Trello card in the "Next Up" list of the Editorial Calendar board. Assign the Trello card to yourself ("put your face on it").
Spend time writing and re-writing a great headline. It helps you narrow your focus, figure out the purpose of the post, and grab people's attention in the first place.
When you begin writing, move the Trello card to the "Drafts" list.
Write the post in Markdown in the our blog's GitHub repo. Add tags to the post. Tags help our readers find related blog posts.
When you're ready for feedback from the team, move the card to "In Review" list and share the Trello card's URL with the team in Slack. Make changes based on their feedback and your judgement.
When the post is ready to publish, give it a published date, merge, and deploy.
Our RSS feed, Zapier, Buffer accounts are set up to automatically work together to link to the post from Twitter, Facebook, Google+, and LinkedIn.
Link to the post from Hacker News, Reddit, Delicious, Pinboard, or other appropriate sites.
Move the Trello card to "Live."
You can tweet from our thoughtbot Twitter account at any time. If the tweet is not time-sensitive, add the tweet to our queue of tweets using Buffer.
Be conversational, casual, and real on the Twitter account. Talk the same way as you would in person amongst ourselves. Be good-humored. Puns are encouraged. Keep the quality high. Aim for every tweet to be a hit. Avoid spelling mistakes. Use punctuation if writing sentences. Respect the people who follow us. To reach the largest audience, don't begin a tweet with a Twitter username.
Some tweet ideas include announcing meetups, open source releases, enthusiasm about a new tool or technique, tips on Git, Unix, and Vim, links to our blog posts, links to others' blog posts if they are excellent and not on the current Hacker News or Twitter cycle at the moment, and Funkmaster Flex.
We have a verified Twitter account and are a Twitter Ads customer. Use the @thoughtbot credentials in 1Password to see analytics such as number of retweets, favorites, replies, clicks, follows, unfollows, and how tweets compare in terms of engagement.
Promoted Tweets campaigns are best for short term campaigns to drive traffic to a website. Target 10-25 similar, relevant @usernames in each campaign. Create different campaigns for different themes of people so that we can track performance per theme.
Ongoing experiments are managed in our "Research" Trello board:
We rigorously research, discuss, and conclude experiments on new tools and techniques. Write up these experiments on the blog at your discretion.
We've created a number of open source libraries to help us perform common tasks and give back to the community.
Our open source libraries do better when there's one person that really steps up to maintain them. Each of our repositories has a leader that tries to keep the repository moving forward. The leader doesn't necessarily do the bulk of the actual work; responsibilities include:
The current open source leaders are listed in Basecamp.
Every thoughtbot developer, designer, and apprentice has commit access to our open source repositories. Some guidelines:
Got an idea for a new library? Found something useful in a client project that you think is reusable? Great! Some guidelines:
thoughtbot is primarily a group of people who care about making awesome software. Thank you for being a part of it.


Error message test
The KANA system does not allow separate error messages as per the Chroma system. Errors are flagged with strong red text, but nothing programatic for screen readers. These two tests are to determine if and where an error message can be presented to screen readers
Example 01: aria-label
applied to the
element
Code
<form action="#">

Address
<input class="input" type="text" id="two">

</form>
Example 02: Hidden information inside
element
Code
<form action="#">

Error:
Email

<input class="input" type="text" id="three">

</form>
Results
The first example works in all browser/screen reader combinations EXCEPT Chroma/VoiceOver. Adding an aria-label
to the label means that the
is completely ignored in this browser/screen reader combination.


DigiBall llc. Store &trade
Welcome To the DigiBall llc. Store!
Home
Fourm
Sign-Up/login
Become a Patron at Patreon!
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License ©


Docstring to Markdown
This piece of code is used within Shed (https://www.github.com/cmry/shed) to
convert sklearn-styled python docstrings to markdown. In this way, only the
code documentation needs to be editted, and the readthedocs is automatically
updated thereafter. To use it, simply type python3 markdoc.py
somefile.py somedoc.md
. Check class help for python usage. Don't forget to
also check the class notes (they are important for docstring format
guidelines).
MarkDoc
class MarkDoc(file_in, file_out, cold=False)
Documentation to Markdown.
This class converts a python file with properly formed sklearn-style docstrings to a markdown file that can be used in for example Readthedocs or jekyll.
| Parameters | Type | Doc |
|---|---|---|
| file_in | str | Python (.py) file from where the docstrings should be taken. |
| file_out | str | Markdown (.md) file where the documentation should be written. |
| cold | boolean, optional, default False | If you want to start the class without starting the reader, set True. |
| Attributes | Type | Doc |
|---|---|---|
| markdown | str | The entire markdown file that will be appended to and written out. |
Examples
Use of the package should just be from commandline with python3 markdoc.py
in.py out.md
. However, if for some reason it is required to load it as a
class, you can just do:
>>> from markdoc import MarkDoc
>>> d2m = MarkDoc('some/dir/file.py', '/some/otherdir/doc.md')
The string of the markdown can be accessed by:
>>> print(d2m.markdown)
Notes
List of common errors and styleguide conflicts:
list index out of range?
-
""" used for non-docstring strings.
-
First Class or method Documentation line has newline after """.
Fix with: """This is the first line.
Here comes the rest.
"""
Class parameters not showing in code blocks?
- Class is not structed new-style (object after class).

- Code is ipython style rather than vanilla (i.e. In [1]: instead of >>>).
tuple index out of range?
-
Mistake in var : type lists (for example var: type).
-
Description also contains a :.
some table parts are not showing?
- Probably included some seperators in there such as : or -----.
methods are garbled?
- Did you name a parameter 'class' or any other matches?
Methods
| method | Doc |
|---|---|
| read | Open python code file, spit out markdown file. |
| handle_classes | Given class docs only, write out their docs and methods. |
| find_declaration | Find class Bla(object): or def method(params): across lines. |
| structure_doc | Produce a structured dictionary from docstring. |
| md_title | Handle title and text parts for main and class-specific. |
| md_table | Place parameters and attributes in a table overview. |
| md_code_text | Section text and/or code in the example part of the doc. |
| md_class_method | Replace class ...(object) with __init__ arguments. |
| md_methods_doc | Merge all method elements in one string. |
| md_class_doc | Merge all class section elements in one string. |
read
read(file_in, file_out)
Open python code file, spit out markdown file.
handle_classes
handle_classes(classes)
Given class docs only, write out their docs and methods.
find_declaration
find_declaration(lines)
Find class Bla(object):
or def method(params):
across lines.
structure_doc
structure_doc(doc)
Produce a structured dictionary from docstring.
The key is the part of the documentation (name, title, params, attributes, etc.), and value is a list of lines that make up this part. Is used to handle the order and different parses of different sections.
| Parameters | Type | Doc |
|---|---|---|
| doc | str | Flat string structure of a docstring block. |
| Returns | Type | Doc |
|---|---|---|
| X_doc | dict | Class or method doc, a structure dict with section; lines. |
md_title
md_title(title_part, class_line=False)
Handle title and text parts for main and class-specific.
Pretty convoluted but delicate method that seperates the top part that should not be part of the title docstring off the document, handles correct punctuation of titles, for both classes and methods. Also detects if there's a description under the first line.
| Parameters | Type | Doc |
|---|---|---|
| title_part | str | Part of the document that starts with triple qutoes and is below any line seperators. |
| class_line | bool, optional, default False | The first line of the docstring by default servers as a title header. However, for classes it should be formed into a normal description. Thefefore, the # marker is removed and a . is added. |
| Returns | Type | Doc |
|---|---|---|
| line_buffer | str | Correctly formated markdown from the buffer. |
md_table
md_table(doc, name)
Place parameters and attributes in a table overview.
The documentation parts that are typed by 'var : type \n description' can be splitted into a table. The same holds true for a list of class methods. These are handled by this method. Table structures are on the top of this python file.
| Parameters | Type | Doc |
|---|---|---|
| doc | str | Flat string structure of a docstring block. |
| name | str | Indicator for the table, can be for example Parameters or Attributes. |
| Returns | Type | Doc |
|---|---|---|
| table | str | Markdown-type table with filled rows and formatted name. |
md_code_text
md_code_text(doc, name, flat=False)
Section text and/or code in the example part of the doc.
If some code related beginnings (>>>, ...) is founds buffer to code, else we regard it as text. Record order so that multiple consecutive blocks of text and code are possible.
| Parameters | Type | Doc |
|---|---|---|
| doc | str | Flat string structure of a docstring block. |
| name | str | Name of the section (Examples, Notes). |
| flat | bool, optional, default False | If there are no code blocks, flat can be used to stop parsing them. |
| Returns | Type | Doc |
|---|---|---|
| code | str | Formatted block of fenced markdown code-snippets and flat text. |
Notes
Please note that this method assumes markdown is uses so-called fenced codeblocks that are commonly accepted by Readthedocs, Github, Bitbucket, and Jekyll (for example with Redcarpet).
This current implementation does NOT allow for ipython styled code examples. (In [1]: Out[1]: etc.)
md_class_method
md_class_method(doc, class_parts)
Replace class ...(object)
with __init__
arguments.
In sklearn-style, each class is represented as it's name, along with
the parameters accepted in __init__
as its parameters, as you would
call the method in python. The markdown therefore needs to fill the
(object) tag that is assigned to classes with the __init__
params.
| Parameters | Type | Doc |
|---|---|---|
| doc | str | Flat string structure of a docstring block. |
| class_parts | list | List with parts that the docstring constituted of. First line should be the class name and can therefore be replaced. |
| Returns | Type | Doc |
|---|---|---|
| doc | str | Same as doc, only now (object) has been replaced with __init__ parameters. |
Notes
Please note that this implementation assumes you're using new-style
class declarations where for example class SomeClass:
should be
written as class SomeClass(object):
.
md_methods_doc
md_methods_doc(method_doc)
Merge all method elements in one string.
This method will scan each method docstring and extract parameters, returns and descriptions. It will append them to the method table and link them in the doc (TODO).
| Parameters | Type | Doc |
|---|---|---|
| method_doc | list | Lower part of the class docstrings that holds a method docstring per entry. |
| Returns | Type | Doc |
|---|---|---|
| methods | str | Formatted markdown string with all method information. |
Notes
On the bottom, dict calls are being done to order several parts of the docstring. If you use more than Parameters and Returns, please make sure they are added in the code there.
md_class_doc
md_class_doc(class_doc)
Merge all class section elements in one string.
This will piece together the descriptions contained in the docstring of the class. Currently, they are written below the top of the python file, and not put into any subfiles per class.
| Parameters | Type | Doc |
|---|---|---|
| class_doc | str | Flat text containing the class docstring part. |
| Returns | Type | Doc |
|---|---|---|
| mark_doc | str | Markdown formatted class docstring, including all subsections. |
Notes
On the bottom, dict calls are being done to order several parts of the docstring. If you use more than Parameters, Attributes, Examples, and Notes, please make sure they are added in the code there.


health climate change Health European adaptation Climate collab: Europe countries {border-style global water px;} #ffffff;} national Office der health, policies Regional impacts risks World effects including Member Region sector Change action extreme people policy risk support Organization Paris States actions change. die reduce systems und development need public responding vulnerability weather United change, emissions environment events evidence impact implementation px; strategy strengthening sustainable {font-family Agreement Nations UNFCCC air billion diseases environmental font-size high human measures mitigation reducing resilience Environment Framework Lancet article assessments associated carbon deaths den des double; frameworks include increasing policies, provide reduction temperatures work Declaration Intergovernmental Kendrovski Ostrava Panel affect border-color border-width challenges co-benefits commitments disaster disease ensure für heat international plans prevent priority protect protection report sanitation sectors solid; summer targets {font-style {font-weight Agenda Commons Conference Convention Creative Die Environ Fig. IPCC Klimawandels Maßnahmen Menne Ministerial Region, achieve address addressing adverse affected aims benefit bold capacity cause changes courier; develop developed draft driver early eine em; example existing facilities flooding framework future general healthy heatwave identify improved mechanisms million natural needs number pollutants possible programme progress promote pt;} region regional response setting specifically spells strategic strengthen use warning werden


{{ partial "head.html" . }}
{{ partial "nav.html" . }}
{{ partial "home_banner.html" . }} {{ partial "home_why_openLookeng.html" }}
{{ partial "statistics.html" . }} {{ partial "blog_news.html" . }} {{ partial "home_video.html" . }} {{ partial "powered.html" }} {{ partial "partner.html" }} {{ partial "FAQ.html" }}
{{ partial "footer.html" . }}
{{ partial "scripts.html" . }}


{% extends "base.html" %} {% block title %}
Medieval catalogues > CISTERCIANS > London > Valuation of goods, 1430
{% endif %}6 identified entries found.
All data was derived from the List of Identifications by Professor Richard Sharpe.
{% if not printing %}
A key to codes used in the List is available (opens in new tab).{% endif %}


Politico
Home
Express Interest
Election Results
View Parties
Vote
LogOut
Vote Your Governorship Candidate
Serial N0
Party
Candidate
Your Vote
1
Mohammadu Buhari
2
Atiku Abubakar
3
Mohammadu Buhari
4
Atiku Abubakar
5
Mohammadu Buhari
6
Atiku Abubakar
Submit Vote


Add an item to the programming list
Your programming list contains {{ itemCount }} items
Add another item to the programming list.
{{ goal }}


|
||||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | |||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |
java.lang.Object | +--com.milk.uberchat.spacebar.SBInteractor
This class does all the interaction with a SpaceBar server. It takes input in the form of filtered messages (filtered through a SBInputFilter) and explicit calls to do stuff. It sends stuff back to the server depending on what it's told to do, and messages etc. along to a SBSystem depending on what gets received from the connection.
| Field Summary | |
private java.util.Vector |
myCommands
|
private SBIdentity |
myIdentity
|
private java.lang.Object |
myIdleObject
|
private java.util.Vector |
myIgnoreLines
|
private FifoQueue |
myInput
|
private boolean |
myIsDisconnected
|
private SBPromptMessage |
myLastPrompt
|
private java.io.Writer |
myOutput
|
private SBSystem |
mySystem
|
private java.lang.Thread |
myThread
|
| Constructor Summary | |
SBInteractor(SBSystem system,
Resender input,
java.io.Writer output,
java.lang.Object idleObject)
Construct an SBInteractor. |
| Method Summary | |
private void |
beDisconnected(java.lang.String errorMsg)
This is called when this object wants to let the system know that the connection has been or should be disconnected. |
ErrorEvent |
doLogin(java.lang.String userid,
java.lang.String password)
Tell the interactor to do a login. |
void |
doLogout()
Tell the interactor to do a logout. |
void |
doRawSend(java.lang.String raw)
Tell the interactor to just send some raw input to the host system. |
void |
doSendExpectSend(java.lang.String send1,
java.lang.String prompt,
java.lang.String send2)
Tell the interactor to do a standard send-expect-send operation. |
void |
doSendExpectSend2(java.lang.String send1,
java.lang.String prompt1,
java.lang.String send2,
java.lang.String prompt2,
java.lang.String send3)
Tell the interactor to do a send-expect-send operation and then expect another prompt and do another send, but if the prompt doesn't come, don't do the following send. |
java.lang.String[] |
doSendExpectSendGather(java.lang.String send1,
java.lang.String prompt,
java.lang.String send2)
Tell the interactor to send a command, expect a prompt, send a response, and then gather the responses from the server. |
java.lang.String[] |
doSendGather(java.lang.String send)
Tell the interactor to send a command and then gather the responses from the server. |
void |
doTypeLine(java.lang.String line)
Tell the interactor to type a line at the host system (i.e., expect it to be echoed back). |
private void |
expectPrompt(java.lang.String type)
This will receive and handle messages until a prompt is received; it then makes sure the prompt is the one that's expected. |
private java.lang.String[] |
gatherResponse()
Gather a response from the host. |
private boolean |
gotPrompt(int count,
java.lang.String type)
This expects the possibility of the given prompt. |
private void |
ignoreLine(java.lang.String line)
Tell the system that the next line to be ignored is as given. |
private void |
ignoreUntilPrompt(java.lang.String type)
This is like expectPrompt, except system messages are silently eaten. |
private void |
promptThenType(java.lang.String type,
java.lang.String line)
This expects either the given prompt or a SYSTEM_PRIVATE line. |
private void |
rawSend(java.lang.String str)
Send the given string to the host system. |
private void |
receiveMessage()
This will receive a message from the input and handle it appropriately. |
private MessageEvent |
receiveSystemMessage()
This will receive a message from the input, handling everything appropriately, except for non-speech MessageEvent s,
which it returns without any processing. |
private void |
threadRun()
This is the main loop of the thread that deals with the connection. |
private void |
typeLine(java.lang.String line)
Send the given string to the host system as typing. |
| Methods inherited from class java.lang.Object |
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
| Field Detail |
private SBSystem mySystem
private java.io.Writer myOutput
private java.lang.Object myIdleObject
private SBIdentity myIdentity
private FifoQueue myInput
private java.util.Vector myCommands
private java.lang.Thread myThread
private SBPromptMessage myLastPrompt
private java.util.Vector myIgnoreLines
private boolean myIsDisconnected
| Constructor Detail |
public SBInteractor(SBSystem system, Resender input, java.io.Writer output, java.lang.Object idleObject)
system
- the system to useinput
- the filtered stream of input from the serveroutput
- the raw output streamidleObject
- the object sent when an idle event happens| Method Detail |
public ErrorEvent doLogin(java.lang.String userid, java.lang.String password)
userid
- the userid to usepassword
- the password to use
public void doLogout()
public void doSendExpectSend(java.lang.String send1, java.lang.String prompt, java.lang.String send2)
send1
- the first string to sendprompt
- the prompt type to expectsend2
- the second string to sendpublic void doSendExpectSend2(java.lang.String send1, java.lang.String prompt1, java.lang.String send2, java.lang.String prompt2, java.lang.String send3)
send1
- the first string to sendprompt1
- the first prompt type to expectsend2
- the second string to sendprompt2
- the second prompt type to expectsend3
- the third string to sendpublic java.lang.String[] doSendGather(java.lang.String send)
send
- the string to send
public java.lang.String[] doSendExpectSendGather(java.lang.String send1, java.lang.String prompt, java.lang.String send2)
send1
- the first string to sendprompt
- the prompt type to expectsend2
- the second string to send
public void doTypeLine(java.lang.String line)
line
- the line to typepublic void doRawSend(java.lang.String raw)
raw
- the string to sendprivate void threadRun()
private void beDisconnected(java.lang.String errorMsg)
errorMsg
- null-ok; if non-null, an error message to report
to the userprivate MessageEvent receiveSystemMessage()
MessageEvent
s,
which it returns without any processing. Handling appropriately
includes adding Runnable
s to the command queue and
telling the system about speech messages. If a message is received
and processed that wasn't a non-speech MessageEvent
,
this method returns null.
MessageEvent
, if anyprivate void receiveMessage()
private void expectPrompt(java.lang.String type)
type
- the prompt typeprivate void ignoreUntilPrompt(java.lang.String type)
type
- the prompt typeprivate boolean gotPrompt(int count, java.lang.String type)
myLastPrompt
variable.
count
- the count of lines to trytype
- the prompt typeprivate void promptThenType(java.lang.String type, java.lang.String line)
type
- the prompt typeline
- the line to sendprivate void ignoreLine(java.lang.String line)
line
- hte line to ignoreprivate void typeLine(java.lang.String line)
line
- the line to sendprivate java.lang.String[] gatherResponse()
private void rawSend(java.lang.String str)
str
- the string to send
|
||||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | |||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |


This is an checkbox layout.
Check one of them.
Then hold the Shift key.
Check one of the lower item.
Every items in between should be checked.
This is written in vanilla JavaScript.
These work well!


=
(Black)
White
Stones left: 9
Stones on board: 0
Stones lost: 0
White has to put stones
Restart
Save Game
Load Game
White
(Black)
=
Stones left: 9
Stones on board: 0
Stones lost: 0