Skip to main content

Notes from building an AI design collaborator · part 11

A screen list lies. I found a whole feature I didn't know we shipped.

· 5 min read

Ask anyone for their product's information architecture and you get a list of screens. Dashboard, invoices, invoice detail, settings. It looks complete. I trusted ours for a long time. Then I built the same map a different way, and it handed me a screen I had genuinely never seen.

A screen list tells you the rooms exist. It says nothing about the fact that "invoice detail" is really seven screens in a trench coat, one for each state the invoice can be in. It says nothing about the screens that only exist for one kind of user. And it says nothing about how you get from one place to another, which is most of what a product actually is. A list gives you the nouns. The product is mostly verbs, and the list quietly throws them away.

So I stopped listing screens and drew a graph

Or rather, I built one and I'm still arguing with it. More on that at the end, because I'd rather tell you what it cost than sell you the tidy version.

The shape is five collections instead of a tree.

Entities, the things the product is actually about. A receivable, an invoice, a partner, a payout. Start here, not with screens, because this is the part that doesn't change when someone redesigns the nav.

Nodes, the real surfaces. Flat, each naming its parent, rather than nested, so a screen can be moved without rewriting the file around it.

Edges, the moves between them. This is the half a list throws away, and it's where the guards live: what has to be true before this transition is allowed.

Patterns, the repeated structures, so "list with filters and a detail drawer" gets named once.

And a coverage ledger, which is derived rather than written, and which is the piece that makes the whole thing honest.

Personas, modes and states stop being separate branches of a tree and become fields on the nodes and edges. That one change is why the thing doesn't explode. "Invoice detail, for this persona, in this state" is one node with three attributes, not eight near-duplicate entries somebody has to keep in sync.

The bit that makes it not lie

Every node, edge and variant carries three things: where the claim came from, how confident we are, and whether anyone has actually observed it. The ledger just rolls up everything still marked unobserved.

So "is the map done?" stops being a feeling. It's done when the ledger for that entity is empty, or when every remaining gap is written down with the evidence for why it's still there. You can't quietly skip the states nobody wants to draw, because they sit in the ledger with your name on them.

Where the map came from, and the screen I'd never seen

The part I didn't expect was where the honest version came from. Not the running app. The code.

We first tried building it by crawling the live product, clicking through and writing down what we found. That crawl was fine work and it missed things structurally, for four reasons worth naming because they'll apply to yours too.

It was navigation-first. Walking the UI surfaces what's clickable and never the state machine underneath. It had a single observation point, one persona, one mode, one account's data, one viewport, one afternoon, so every gap is really a sampling artifact. It was a static snapshot of a dynamic system, and a crawl at rest cannot see a transition, a side effect, or a guard, especially since it rightly refuses to click the destructive ones. And it was siloed, so knowledge that already existed elsewhere got paraphrased into it rather than linked, and then drifted.

Modelling entity-first instead, straight from the code, the first thing that fell out was that the receivable detail screen had no node in the old map at all. None. Despite existing in our captured page snapshots and in the sitemap, sitting there in the product, reachable. It came back into the graph with nine status variants, of which exactly one had ever been observed and eight were owed.

I was a little embarrassed. I'm meant to know our product.

The rule that keeps it honest

Code and reality don't always agree, and that gap is the interesting part. The code is what could exist. Production is what does. Something can be alive in the code and dead in the product, or busy in the product and already being deleted out of the code.

So the rule we settled on, and the one I'd hand anyone doing this, is that absence of observation is not absence in production. Your analytics never catching a state doesn't mean nobody hits it. Maybe it's rare. Maybe you're just not measuring it. So you don't delete the thing you didn't see. You annotate it. Code says it's here, we didn't catch it live, here's what we know.

The moment you start deleting everything you didn't personally witness, you stop mapping the product and start mapping your own instrumentation. Which is a much smaller and sadder thing to be the owner of.

Where it actually stands

I said I'd be straight about this, so.

The graph is a proof of concept and it is currently parked. It beat the crawl, demonstrably, and that's what it was built to find out. Then it stalled at four entities out of roughly sixteen, with one observed variant each in most cells, and I moved it out of the package the team actually uses so nobody would mistake it for finished. The old screen list is still what our workflow reads.

Modelling a product this way is a lot of work, and most of that work is the boring part, filling in the cells rather than designing the schema. I'd still do it again, because the four entities we did model taught me more about our own product than the year before them. But if you take one thing from this post, take the ledger and the provenance fields rather than the graph. You can bolt those onto the screen list you already have, this week, and the first thing they'll do is tell you how much of it nobody has actually checked.


Part of a series on building an AI collaborator for our design team at Xflow. Each post stands on its own.

  • information-architecture
  • ai

The work behind the series

Designing the Instructions

This post is one thread out of a three-month project: an AI design collaborator for a payments team. The case study is the whole of it: what worked, what broke, and what is still unproven.

Read the case study →