The Canvas Is the Filing Tax Under a Nicer Name

Free positioning sounds like freedom and quietly makes arranging compulsory. What shipped instead was one number line and no special case anywhere.

Date

/

Category

UI/UX

UI/UX

/

Writer

Keon Hwui Kim

Keon Hwui Kim

The obvious next move, after building a place for a person's own things, is a canvas. Free positioning. Drag anything anywhere. Arrange your world.

It is what the best tools in this category do, it is what a decade of design software has trained everyone to expect, and it is what almost everyone asks for. I built toward it for two weeks, and then rejected it, and the reason is one I did not see coming.

A canvas is the filing tax wearing better clothes

On a true free-form canvas, every object has an absolute position. Which sounds like freedom, and is, but it carries a requirement that nobody states.

If position is the organising principle, then an object without a considered position is a problem. It is floating somewhere arbitrary. It makes the board look unfinished. It nags.

So the canvas asks you to arrange everything. Not with a dialog. With the layout itself, continuously, as a standing obligation that grows with the collection.

Sixty-five items in and it was already obvious. I was spending time tidying a board instead of using a field.

That is the filing tax. The same tax in a different currency. Instead of choosing a folder you are choosing coordinates, and the choice is just as premature, just as unrewarded, and considerably more frequent.

Any system where the unarranged state looks broken has made arranging compulsory, whatever its marketing says.

What the requirement actually was

Once I stopped trying to build a canvas and started writing down the requirement, it got much easier:

Arrangement must be available and never required, with no mode, no toggle, and no special case anywhere in the code.

That last clause did most of the work. A mode is an admission that you could not unify two behaviours. Any design where the answer involves an if (arranged) branch has failed the requirement, because that branch will multiply, and in two years every function that touches layout will have it.

The solution is one number

// Every item has a rank.

rank(item) =
    item.placedRank          // if a person moved it
    ?? -item.arrivedAt       // otherwise: negated timestamp

// Sort by rank. That is the entire layout rule.

// Untouched items sort newest-first, automatically,
// because a more recent timestamp negates to a smaller number.

// A moved item sorts where it was put, permanently,
// because its rank is now the person's, not time's.

// Both live on ONE number line.
// There is no flag. No mode. No second code path.
// "Put it back" simply clears placedRank.
// Every item has a rank.

rank(item) =
    item.placedRank          // if a person moved it
    ?? -item.arrivedAt       // otherwise: negated timestamp

// Sort by rank. That is the entire layout rule.

// Untouched items sort newest-first, automatically,
// because a more recent timestamp negates to a smaller number.

// A moved item sorts where it was put, permanently,
// because its rank is now the person's, not time's.

// Both live on ONE number line.
// There is no flag. No mode. No second code path.
// "Put it back" simply clears placedRank.
// Every item has a rank.

rank(item) =
    item.placedRank          // if a person moved it
    ?? -item.arrivedAt       // otherwise: negated timestamp

// Sort by rank. That is the entire layout rule.

// Untouched items sort newest-first, automatically,
// because a more recent timestamp negates to a smaller number.

// A moved item sorts where it was put, permanently,
// because its rank is now the person's, not time's.

// Both live on ONE number line.
// There is no flag. No mode. No second code path.
// "Put it back" simply clears placedRank.

The default order is chronological, newest first, which requires nothing from anybody. Move something and it stays where you put it, because its rank belongs to you now. Move it back and time takes over again.


Free canvas

One number line

Default state of a new item

Arbitrary position, looks unplaced

Correct position, by time

Cost of ignoring arrangement

The board degrades

Nothing

Cost of using arrangement

Must maintain it

One drag, permanent

Code paths

Position always explicit

One comparison

What it asks of the person

Arrange everything

Arrange nothing, or something

Table 1. The canvas is more powerful and more expensive on every single item, forever.

Folders, and why stacking replaced them

Grouping still had to exist. Folders were the obvious answer and I did not build them.

A folder is a new kind of object. It has to be created, named, opened, closed, nested, emptied, deleted, and reasoned about whenever something is dragged near it. It needs an empty state. It needs a rule for what happens when you delete one containing things. It needs a decision about whether it can contain itself.

Instead: stacking. One field on an item saying it sits on another. That is the entire model. No new object type, no hierarchy, no navigation, nothing to name before you can use it.

A stack is currently one level deep, and that limitation is deliberate rather than unfinished. Depth is where folder systems begin their long decline into a place you organise instead of a place you keep things. One level covers grouping. Two levels invite a taxonomy.

What made me trust the decision

I did not reason my way to this. I reasoned my way toward the canvas, built enough of it to live in, and the material told me.

Sixty-five items was all it took. At sixty-five, the cost of arrangement is already visible and the benefit is already not. I would have needed thousands of items to prove the canvas wrong in the abstract. I needed sixty-five to feel it wrong, which is faster and considerably more reliable.

So the rule I would offer, if there is one:

Build enough of the wrong thing to live in it.

Not a prototype you demo. Prototypes are evaluated, and evaluation is a different mental posture from use. A version you actually use, with real material of your own, over enough days to get annoyed. Annoyance is the highest-bandwidth design signal there is and it cannot be simulated, predicted, or obtained from a user test, because a user in a test is also evaluating.

The general shape

There is a family of design decisions that look like they are about capability and are actually about default state.

  • Tags versus none. An untagged item in a tag-based system looks incomplete. The system has made tagging compulsory without saying so.

  • Titles versus none. An untitled document with a blinking title field is a request. The same document titled with its first line is a document.

  • Profiles versus none. An incomplete profile with a progress ring is an obligation that arrived with the account.

In each case the feature is optional and the default state is not, because the interface renders the unfilled version as deficient. The question to ask of any optional feature is not whether a person can skip it. It is what the product looks like when they do.

If the answer is "unfinished," it was never optional.

Related reading

More from the Moc Mallen Studio journal