Skip to main content

Honest about the model

Designing Lists, a working surface for a product-matching model that customers couldn’t yet fully trust.

Overview

EDITED is a market intelligence platform used by some of the world’s largest retailers and brands to track competitor assortments, pricing and promotions. When I joined as Senior Product Designer in 2020, the company had a Machine Learning (ML) product-matching model in development and a feature called Saved items that was supposed to expose it. Saved items had a bad reputation, and customers weren’t using it. Lists was the rebuild. It shipped in April 2021 as a spreadsheet-like working surface where customers could pull together small comparison sets of products and run the ML model against them. It was the first of three product-matching tools I designed at EDITED, followed by Product Matching v1 and EDITED Match, each written up as its own case study. This one is about Lists on its own terms.

Note: This case study describes work completed at EDITED. Client names and commercial details have been generalised to respect confidentiality, and any figures shown are illustrative rather than sourced from EDITED.

The challenge

Saved items had roughly 1% usage among existing customers. That number was the quantitative shape of the feature’s bad reputation, and it was what I was handed on day one. The flow was three steps. You added products to a side-panel clipboard as you browsed, one at a time. You turned the clipboard into a board when you were ready. You opened the board to see your selection laid out as a table. And then the table did nothing. You couldn’t sort it, reorder columns, add more products, or do any of the comparison work customers had been building the board to do. So they exported to Excel and did it there.

EDITED Lists Quote-Customer
A research note from a customer who’d built a selection and then hit the wall: there was no way to add it to an existing list without starting over. The commit-step problem in one observation.

The model the company had been building in parallel, an ML system that could match the same or similar products across retailers and regions, could now produce results but couldn’t yet be trusted to run unsupervised. That was the gap Lists had to close. We needed a working surface that let customers bring their own comparison logic to the table and use the model as one action within it, not a UI that pretended the model was more finished than it was.

The team was small. One PM, who was early in his career and on his first big feature. An engineering team implementing against AG Grid. The data science team working on the underlying model alongside us. No one expected this to be the last word in product matching at EDITED. We treated it as an MVP, with the explicit job of shipping something good enough for customers to use, so we could learn from real usage what to build next.

How I approached it

The reframe was small but load-bearing. Lists wasn’t a checkout, it was a working surface. Once I saw it that way, the rest of the project had a shape.

The first half was discovery. Sixteen customer interviews across three rounds, from luxury marketplaces to outerwear brands to sportswear retailers, turned into an affinity diagram grouped around the jobs customers were actually trying to do: view and customise tables of products, group them for comparison, match products across retailers, pull their own data in as custom rows and columns, and run simple calculations. The features we shipped map directly onto those themes. The ones we knew we’d need later (formulas, alerts, scheduled exports) got parked as ambitions.

EDITED Lists JTBD-Affinity-Mapping
Sixteen interviews grouped into themes that framed the feature scope. The themes the model couldn’t yet support (formulas, alerts, scheduled exports) got parked. The rest became Lists.

Testing with internal colleagues before each build cycle

Alongside the customer work, I ran unmoderated Maze tests on Figma prototypes with internal EDITED colleagues before each build cycle went into production. It surfaced friction at a point where changing a pattern still cost a design revision rather than an engineering rebuild. Most missions landed at 90% or above for direct success. The ones that didn’t became the next sprint’s redesign list. The company asked for more of these runs, and it became a pattern I carried into the later product-matching projects. Once Lists was in the wild, I watched Fullstory usability videos to see how the shipped version held up against what discovery had said customers wanted.

Replacing the basket, and framing the craft

The call that shaped everything else was the table library. We used AG Grid rather than build a spreadsheet-style component from scratch. That was a pragmatic decision for shipping speed, but it meant design had to live in a real push and pull with the library. Design too freely and the component wouldn’t support it. Design too tightly to it and Lists would feel like a generic data grid rather than a considered product surface. Most of the craft of this project lived in that gap.

The first thing the reframe killed was the clipboard-then-board flow. If Lists was a working surface, there was no reason to separate assembling a set from working on it. Customers already used EDITED’s market intelligence filters every day to find products, so Lists plugged straight into that habit: filter a view down to the products you cared about, then either add an individual product to a List or send the entire filtered search into a List in one move. Once inside, the same filters and the ML model were available as in-context actions. The commit step disappeared. You could keep adding, removing, grouping, matching and pinning without leaving the surface.

EDITED Lists Select product and add to list

Running the rename as its own project

The rename rode with the redesign. Saved items became the Grid internally while we rethought positioning, then Lists at release, because the name fit the user’s actual job. The change was run as its own small project: a before/after audit of every surface it touched across the global dock, breadcrumbs, search placeholders, cards and dashboard CTAs, so the transition felt consistent rather than patched.

Grouping, and the stacked-table decision

The clearest example of the push and pull with AG Grid was grouping. A customer comparing a product across markets (say, this shoe in the UK, France and Germany) could see the matches as one unified table with collapsible group headers, or as a stack of smaller tables, one per market. I chose stacked tables, because a stacked layout let us place a per-table trigger at the bottom of each group that surfaced product-matching suggestions for that group in particular. A unified table couldn’t have supported that cleanly. The cost was real, mostly on the engineering side, where keeping data in sync across the separate tables took ongoing work. On EDITED Match, two years later, the trigger wasn’t needed and native grouping became the right call. That’s a different case study.

EDITED Lists Stacked tables with per-group match trigger
Stacked tables let a per-group match trigger sit at the foot of each table. A unified table couldn’t have supported that cleanly, which is why the trade-off in engineering effort was worth it.

Pinning the baseline

The baseline product pattern was the other hinge. Every Lists search needed one reference product to anchor the comparison against, so all the price, availability and match metrics could be calculated relative to it. The pattern that landed was pinning a row as the baseline, with every metric in the table recalculated from that pin. It came out of an engineering planning conversation in June 2020, when we realised AG Grid’s pinned-row behaviour could carry the baseline semantics if we designed around it.

Pinning a row as the baseline meant every metric in the table recalculated from that pin. AG Grid’s pinned-row behaviour carrying the baseline semantics was the unlock.

The detail I remember most fondly is the Group ID custom column. Customers kept asking to group by their own internal logic, their own trade strategy, their own tagging, none of which mapped to any column EDITED provided. Rather than build a full data integration (out of scope, out of budget), I added a column customers could populate with any identifier they liked and group by. A tiny extension to the library’s native grouping that unlocked a meaningfully more flexible comparison story without any new infrastructure.

EDITED Lists Group-ID
EDITED Lists Lifestyle

Designing the matching mode

Matching was where language and state mattered most. The button settled on Find matches, paired with Close matches when the mode was active, after several iterations that read more like UI plumbing. The model could return up to a hundred candidates per baseline product, but the first ten to twenty were typically close to exact and the long tail degraded into loose similarity. Showing everything at once would have buried the good matches under the weak ones, so we paginated five at a time with the tightest matches first. A harder rule was what to do with the vs-baseline columns while candidates were still sitting in the suggestions panel. Showing the deltas on unadded candidates would have implied they were already part of the comparison. They weren’t. So matches only carry vs-baseline calculations once they’ve been added to the List. The suggestions panel is the candidate pool, the List is the committed set, and the visual distinction between them had to earn its keep.

Matching mode, suggestions panel, move-to-List interaction and pagination

Because a product existed in exactly one place at a time, adding a match physically moved it from the suggestions panel into the List. The move had to be legible. A disappear-and-renumber would have broken the mental model. The interaction I designed for was a short sequence: the Add-to-List button confirmed with a state change, the row lifted out of the suggestions panel, a new candidate slid into its place from the pool, the total count ticked down, and the new row appeared in the List above with the vs-baseline calculations now applied. About half a second end to end. Enough to feel deliberate, not enough to feel choreographed.

EDITED Lists Comparison-Whole List
A baseline row with candidates below, paginated tightest-first. The relocation animation when a match moves into the List is what made the suggestions panel and the committed set feel like different states.

Scoping the match

The last piece was the retailer filter. Left alone, the ML model matched against every retailer in the catalogue, which was too wide for most customers. A luxury brand wanted a defined competitive set; a department store wanted to exclude its own sales channels. I prototyped the filter as a separate page, a side panel, and a modal. The filter held around five thousand retailers and needed a split layout (a selectable list on one side and a live summary of what was in scope on the other). At that complexity, a side panel would have felt cramped against the table it was supposed to serve, and a separate page broke the flow. The modal won on focus.

EDITED Lists Retailer-Filter
Five thousand retailers in a split layout, with the live summary on the right. The modal won over a side panel because the table behind it needed to stay in view, and over a separate page because the flow couldn’t break.

Base retailers as a global setting

A customer’s own retailers (their brand-owned stores, their marketplaces) sat at a global, account-level settings page rather than inside the per-List filter, because “who am I” is a stable preference and didn’t belong in a per-search control. Inside the modal, base retailers were excluded by default, with an opt-in toggle to include them when the use case called for it. A small gear icon next to the toggle linked out to the global settings. Two levels of scope, one per-session and one account-wide, held cleanly apart.

Outcome & reflection

Lists shipped in April 2021, alongside the Product Matching Luxury Universe, as the first real customer-facing surface for the ML model. Customer response at release was the validation the MVP framing had been aiming for. A global luxury marketplace asked for immediate access. A premium outerwear brand asked to be in any beta we ran. For a feature that had been carrying 1% usage a few months earlier, the shift in posture was the clearest signal we’d got the direction right.

The more interesting outcome is what Lists taught the team. Customers used it, and in using it they showed us the shape of the next problem: the luxury customers trusted the matches and wanted to scale, the fast-fashion customers needed more human-in-the-loop review than Lists was built for, and the per-list scoping cap was going to be the bottleneck for the workflows that worked. The cap wasn’t a limitation of Lists, it was a property of the approach itself. Every List had to be anchored to a baseline product, and that framing couldn’t stretch to the larger, unanchored matching jobs the luxury customers were asking for. Product Matching v1 and EDITED Match got built as separate products rather than extensions of Lists for that reason.

The thing I carry forward from Lists is how much of designing for ML is about being honest about what the technology can do today. The temptation was always to skip ahead and build the proper product-matching tool. Lists worked because it didn’t. It gave customers a working surface they could get value from at the model’s current reliability, and it gave us real usage data to point the next iteration at. That’s the pattern: design for what the technology supports now, build toward what it will support later, and trust that the product will tell you when the frame has to change.

EDITED Lists Lifestyle Footer