This post advocates a design philosophy I’m calling view-inspired, model-driven. We’ll look at a domain, task-tracking and project management, and examine the failure of an alternate philosophy, ‘view-first’, using Trello as the case study. I don’t mean to pick on Trello specifically, because once you are on the lookout for it, you can see the symptoms of view-first design in many applications.
Good task tracking and project management is based on 1 important principle:
By default, the user should only have to look at the tasks that are currently relevant to them.
This sounds kind of obvious, but it’s amazing how poorly existing productivity and task-tracking apps facilitate this. More on that later.
A task might not be relevant now because:
Note: We can view all these as a special case of a more general principle. Each task has what we’ll call a relevance function. Given various inputs, the relevance function indicates whether the task is currently relevant. “Hang picture frame in living room” might have a boolean input for “whether the user is at home”.
More technically, we want both perfect precision (only relevant tasks are shown) and perfect recall (all such relevant tasks are shown; none are left out). With bad recall, the user isn’t shown and forgets to do an important task. With bad precision, the user is overwhelmed by lots of irrelevant info.
Poor recall is pretty obviously a problem, but some very bad user adaptations happen with poor precision:
If you’ve ever tried to use a task-management system and gave up after a while, maybe you recognize some of these failure modes.
Trello is a nicely polished app billed as being capable of helping you “organize anything”. In Trello, there are “boards”, “lists” (within boards), “cards” (within lists), and “sub-card lists” (within cards). Because of this rigid hierarchy of items (a card cannot exist on two boards), attempts to achieve good precision and recall are doomed. The essential problem is that:
To combat these limitations, Trello has some support for “labels” and “filters”. But bolting on features in an ad hoc fashion to compensate for a poor underlying model never works out nicely.
Let’s look at a case study. Suppose Carol and Dave are a young, conscientious couple looking to get organized with all the tasks in their life. Dave is a graphic/web designer and blogger and has his own consulting business, while Carol is an attorney at a small 4-person law practice. Both Carol and Dave have lots of tasks and items they’d like to track that aren’t relevant to the other. Dave might have various sales leads that he’s tracking, workstreams for different active clients, random design ideas, blog post ideas, and so on. And Carol and the partners at her firm have info about active clients and their cases, and lots of other items that aren’t relevant to Dave (and indeed, Dave should not be allowed to even see some of this information that is client-specific).
At the same time, Carol and Dave have lots of tasks that are common to both of them. There’s day-to-day errands and tasks (“go grocery shopping”, “buy new shoes”), but perhaps Carol and Dave are in the process of finding and purchasing a home. There’s lots of tasks here, related to getting a mortgage, looking at homes, making offers, and eventually moving. Although most of these tasks can be anticipated in advance, there are a lot of dependencies between the various tasks and only a few can be actively worked on at a time.
How might Carol and Dave use Trello?
With great discipline and lots of processing time, people who are really committed to using Trello can probably find a workflow that gets them 85% of the way to a fluent, usable system. Then again, with discipline and lots of processing time, most people can also maintain inbox 0.
Trello isn’t a bad application. It’s polished, pleasant to use, and works quite well in some contexts. But it’s based on a bad model, the result of lazily importing some physical-world metaphor into the design of software. It has a surface-level intuitiveness and ease of use, but breaks down for more complex scenarios.
We are seeing here the deficiencies of a “view-first” design philosophy. The application was designed around a particular view. The view is polished and pleasant for what it is, but other views, or even more dynamic views, are just not well supported because the underlying model is too limited.
I advocate a design style I call “view-inspired; model-driven”. It’s fine to use views as inspiration for an application, since we are often driven to design applications by thinking of a possible end user experience. But these ideas should just be taken as inspiration to drive development of a clean, general model that can express both the views you think of now and the ones you (or your users) might think of later.
I hesitate to call this a model for a task management system, because it’s actually something more general-purpose. But it can certainly be used for task management. The model is simple and consists of two parts.
First, we have a set of cards. I’m not calling the cards “tasks” since a card might be used for other things (ideas, notes, whatever). Each card has a set of fields. Here are a few example fields:
due-date, of type
Date, indicating when the card should be completed
Booleanindicating whether the card is ‘archived’ or not
List Card, indicating any dependencies
List String, with some labels assigned to the card
List Userwith some users assigned to the card
List Userindicating what users may view the card. The ‘Public’ user means anyone, without needing a login.
List Userindicating what users may view and edit the card
Not all cards must have the same fields, and in fact my comments on the above fields are just comments or conventions. With the exception of
writable-by, no fields are magic or built-in. The meaning of these fields is derived by the queries used to build views of this set of cards that constitutes the model.
The other part of the model is a list of views. A view denotes a
Set Card -> List Card (we might call this the “query”), plus a
Card -> Card indicating how cards created within that view should be initialized. Perhaps a view also includes a rendering function to be used for each card returned by the query. The general idea here is the view converts a set of cards to an ordered list of cards, chooses how each card is displayed, and also controls how cards created within that view are initialized. Views are first-class. A user saves these views to their account, but can also share them with others. (Unlike every other app for task tracking that comes with a rigid set of predefined views.)
I won’t go into detail on the query language, but it should support common queries like:
Note that when creating cards, we may do so in the context of some view. Any labels, due dates, or other fields selected by that view should by default be included in any newly created cards, and the user may opt out of these defaults if they wish. So if I have a view which is “tasks assigned to me due this week”, and I create a card within this view, it should be initialized with a field that assigns the card to me, and the default due date for the view might be Friday of the current week.
By not special casing things, we get a more powerful, flexible system that is simultaneously simpler to implement. Note that with this model we can compose views, basically adding subfiltering.
That’s it for the model. I’ll leave it as an exercise to figure out a nice UX for creating and interacting with this model.
We often think about views first because views are concrete, and it’s what we interact with directly when we use software. But actually designing software ‘view first’ is problematic because it leads to rigid models that aren’t flexible enough to support the myriad of creative ways that people use your software. It also leads invariably to feature creep—when your model is overly influenced by some concrete views you had in mind during design, it invariably ends up insufficiently general purpose. So as your software becomes more popular, you start adding one-off ‘features’ to support concrete use cases that your users are asking for. A few years pass of this feature creep, and you have a bloated, complicated piece of software that no one gets joy out of using.
On the other hand, ‘view-inspired; model-driven’ means picking a general-purpose model, with a rich algebra, right from the start, and then crafting views (or letting users craft views) that fetch data from and allow various interactions with the model. There are no ‘features’ to add, no feature creep, and the implementation can be much much simpler.comments powered by Disqus