Everything these days have labels: emails, GitHub issues and pull requests, todo list items, saved articles, etc. More importantly, labels often are the only available tool for classification. Yet, bad labels can increase clutter and confuse more than they inform.

In this post I’ll make a case for structuring labels, using GitHub issues and pull requests labels as an example.

Sending the right message

One label, two meanings

It’s often underlooked that a label has two meanings: one when it’s applied, and one when it isn’t. A label adds a piece of information not only to items that bear it, but also by contrast to items which don’t. One striking example is the help wanted label on GitHub: using this label on any subset of items conveys (deliberately or not) that help is not desired on the others.

Take the time to consider the implicit classification of unlabeled items. When help wanted exists as a label, the default situation is implicitly that help isn’t wanted. Rename this label to help not needed and you just reversed that perception. Such details may be of importance when aiming to build a welcoming project.

The label soup

The default set of labels on GitHub is the following: bug, duplicate, enhancement, help wanted, invalid, question, wontfix.

The problem with a flat collection of labels is that there’s no obvious purpose, and no explicit relationship between them. For example, which labels are mutually exclusive? It might be generally accepted that an issue is either a bug or an enhancement, but what about question? Can I have a bug question and an enhancement question? What about a invalid question?

A flat collection of labels is an heterogenous pool of values to pick from: like anything which doesn’t have clear intent, it becomes subject to interpretation. Luckily, we have a nice tool to give meaning to values: types!

A structured approach to labeling

Thinking with types

If you were writing code, how would you define a type to hold the metadata you care about? Taking an imaginary set of labels as an example, this is what the typical label organization would look like when expressed as code:

1
2
3
4
5
6
7
8
9
10
11
12
13
type Label int

const (
    API Label = iota
    Bug
    CodeReview
    DesignReview
    Networking
    Question
    Storage
)

type Metadata []Label

For those unfamiliar with Go, this is simply defining a Label enumerate, and the Metadata type as a collection of Label values.

That’s the “label soup” described above, but we can be more expressive about the intent and constraints:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Area int

const (
	API Area = iota
	Networking
	Storage
)

type Kind int

const (
	Bug Kind = iota
	Feature
	Question
)

type Status int

const (
	DesignReview Status = iota
	CodeReview
)

type Metadata struct {
	Areas  []Area
	Kind   Kind
	Status Status
}

The Metadata type properly captures that a given item can have multiple areas, a single kind, and a single status. Similarly, it’s now clear that an item is either a Bug, a Feature, or a Question.

Expressing as labels

Now armed with the model we want, we need a way to represent it with labels. Labels are typically just strings, so we need some kind of micro-format: the way it’s set up on the Docker project and what I use throughout my tools is a straightforward field/value syntax.

Area Kind Status
areas/api kind/bug status/design-review
areas/networking kind/feature status/code-review
areas/storage kind/question  

This gives us quite a different approach to labeling: setting a label is assigning a value to a named field of a metadata type. The intended structure:

1
2
3
4
5
Metadata{
    Areas:  []Area{ API, Networking },
    Kind:   Bug,
    Status: CodeReview,    
}

… translates to the following set of labels: areas/api, areas/networking, kind/bug, status/code-review. Easy enough!

Of course, none of the services I know would technically enforce that an item can have multiple areas/ but a single kind/. The best we can do is rely on the added explicit purpose on each label, and on the pluralization of areas/ field name to signify a list over a scalar value. On the Docker project, we took the extra step of documenting the labels.

As a bonus: structured labels make it way easier to visualize data according to specific attributes. For example breaking down GitHub issues into areas, kind, and version:

Issues breakdown.

Summing up

Whether on GitHub issues, emails, todo list tasks, or unread articles, labels are often are only tool provided for classification, and getting them right is key to being able to manage large sets of items. Take the time to consider their explicit and implicit meanings, and how much is subject to interpretation. A structured approach to labeling can truly help here.

As the implementer of a service that supports labels, define the default set wisely. And if it happens that your service is targeted at a technical audience, I think it’s worth exploring how a labeling system could embrace a structured approach and offer the ability to specify a model in order to enforce constraints.

I work in a fast paced startup as the manager for a dozen (amazing) engineers who typically deal with 2 to 5 simultaneous tasks at any given time. As a result, one aspect of my job is to keep track of all of those items and know:

  • What we’re doing, and what’s the current status.
  • Who is involved on each, and whether the load is appropriately distributed.
  • Whether the team needs help to make progress.

I’ll use this specific example to describe how I moved away from spreadsheets in favor of Airtable and what it buys me.

Your typical spreadsheet

Here’s a simplified and anonymized version of the spreadsheet I had been using so far:

Task management spreadsheet.

All the information is there, but:

  • Getting from zero to a spreadsheet like this one takes some time. You need to structure as a table, set up data validation, set up conditional formatting.
  • Maintenance is painful: for example adding a category requires updating the scaffolding accordingly.
  • Data cannot easily be queried: filtering allows to narrow down to a subset of categories or statuses, but that’s basically it.

In other words, there’s some overhead and hygiene involved in making a spreadsheet behave like a database.

Introducing Airtable

I would describe Airtable as a relational database wrapped under a familiar and accessible spreadsheet UI. Where spreadsheets enforce no particular structure, every sheet in Airtable is really a table storing records under a well-defined schema . By design, implementing the example spreadsheet above in Airtable takes no more than a minute because the only effort resides in the schema definition: once done the UI is meant to manage records according to that schema.

We’ll start with Airtable’s ability to import existing data (either through CSV or copy/pasting fields), go through each column to update its data type (hence defining our schema), and tada:

Migrating to Airtable.

At this point it may seem like we traded one UI for another, but there are already concrete benefits:

  • We get a nice form UI for adding and managing records.
  • The table structure and schema are enforced.
  • Maintenance is trivial: for example, I can add a category in a second.

But it’s gets much better.

Leveraging views

Airtable has a straightforward UI for grouping and sorting: pretty much Pivot Tables for humans. For example, we can trivially group tasks by category, and sort by ascending update date.

Grouping by category.

From there you can persist the settings as separate views and even create non-grid views, such as a Kanban board.

The Kaban view.

Leveraging relations

Let’s see how the “relational” dimension of Airtable can help us improve further. So far the “Who” column is just free text: we can create a “Team” table with the team members, and convert the “Who” colum to a Linked Record.

Leveraging relations.

The “Who” column being a linked record gives you everything you would expect from a relational database. Airtable’s UI is again really key here, as it’s trivial to select a record from the People table from the Tasks table, and even create new People entries as a task is being added.

More importantly, Airtable automatically created the other side of the relationship in the People table: we basically get the list of task assigned to each individual team member for free.

The other side of the relation.

Summing up

Spreadsheets are great, but for anything that ressembles a database with structured records the boilerplate for the initial setup and maintenance cannot be ignored. This is one aspect where Airtable really shines: by providing a familiar spreadsheet-like UI to what is really a relational database under the hood, it eliminates a lot of the overhead while providing some immensely valuable benefits.

In this post we only scratched the surface: a thousands more things can be achieved when you start digging in Airtable’s API. More advanced use cases have been described by Simon Eskildsen on his blog.

I’ve been reading composition books and blogs in an attempt to discover new ways to organize the frame. One of those ways that I’m currently experimenting with is called dynamic symmetry, and is discussed extensively on James Cowman’s blog.

In this post, I’m exploring how to add the dynamic symmetry grid as a custom overlay in Lightroom.

Lightroom Loupe Overlays

At the time of writing, Lightroom CC is in version 2015.5.1 (1073342).

Lightroom comes with several crop guide overlays, but unfortunately the dynamic symmetry grid isn’t part of them. Although there is no way at the time of writing to customize the overlay grids, another feature can be exploited as a workaround: loupe overlays.

The original goal of this feature is to put an image in its context of use, such as adding all the surrounding text of a magazine cover. You will find this under the View menu:

The Loupe Overlay menu.

By creating a set of custom images, we can use this feature to our advantage to overlay a dynamic symmetry grid, using the Command + Option + O shortcut to toggle it:

The Dynamic Symmetry overlay.

Issues and limitations

At the time of writing, the feature is unfortunately super buggy, and seems to crash Lightroom only minutes after being enabled for the first time.

Another minor problem is that loupe overlays doesn’t follow the image orientation. In other words, you need both a horizontal and vertical grid images and manually switch between the two.

Download the overlay files

I created different flavors of the grid, for either light or dark lines, and horizontal or vertical orientation:

I created this blog during the Christmas 2015 holidays. One of the craziest year of my life was coming to an end, and for some reason I started to feel the desire to write about what I did and what I learned. I really wanted the first post to be something positive about my experience.

At the end of that week, my colleague Ian Murdock passed away. It took me 6 months to post this. A positive memory, one that I will absolutely never forget.

It was summer in our San Francisco office when, as almost every day back then, someone dropped a few papers on my desk for an upcoming interview. A quick glance at the first page. Ian Murdock. Ian Murdock? How the hell am I supposed to interview Ian Murdock? Good news, I’m scheduled to do the interview together with Jess. Still, how the hell are we supposed to interview Ian Murdock? What do you ask somebody like him?

Well, you ask him to tell his life, which is exactly what we did. Of course we knew Ian for Debian, but we had no idea about tons of the other things he did. He went on for 45 minutes, with an amazing simplicity, and amazing humility, as he was essentially teaching two kids about the history of modern computer science. A history he vastly contributed to write.

We thanked him for this time with us, which made strictly no sense given the circumstances. And as we were about to leave, he told Jess the most amazing things: “by the way, I follow you on Twitter and read your blog: really cool stuff!”.

Crossing your path was an immense honor.

We miss you Ian.