# BidBrain UX audit, the purchases page

Audit date 2026-08-28. Read against the live code (`bidbrain/render.py`,
`render_purchases()`, lines ~1434 to ~5720) and the running app at
`http://127.0.0.1:8765/purchases.html` with the real 213 purchase records.
No code was changed. Every number below was measured, not estimated.

> **Part 2 added the same day** covers the cockpit (Live Auctions) and how the
> two pages should work together. See sections 9 to 12 at the end.

---

## 1. Executive summary

1. The purchases page does not actually have too many filters. It has four
   (search, bought date, platform, view). It has too many **buttons that are
   not filters** sitting where filters should be, and a sort you cannot see.
2. The single worst problem is not clutter, it is that **at 860px and below
   the whole page becomes 2,633px wide**. Your toolbar sits at x=1,876. On a
   phone every control except the platform tabs is off screen.
3. One CSS line causes it: `.appshell{align-items:flex-start}` combined with
   `flex-direction:column` in the 860px media query. Forcing `stretch` in the
   browser cut the overflow from 2,258px to 139px. That is your first fix.
4. Sorting is invisible. Column headers sort on click but carry no arrow, no
   hover cue, no tooltip until after you have clicked one. Nobody would find
   it.
5. **86% of the money on your working list is provisional** (18 of 21 Due in
   rows are `payment_estimated`) and the only warning is inside a hover
   popover on a different column. That is the most likely way to misread a
   number.
6. The Bought date filter silently drops any car with no bought date. That is
   **160 of 213 records**. Setting a date range quietly hides three quarters
   of your history.
7. Two of your five platform tabs (Auction4Cars, Dealer Auction) can never
   show anything, no reader feeds them. Dead controls in your most cramped row.
8. Reset clears 3 of the 6 things it looks like it should clear. Platform,
   view and sort survive it.
9. Do the aggressive consolidation (Option C). It gets the default view to
   zero controls and every journey you named to 1 or 2 taps.
10. Half the journeys in your brief do not exist on this page at all, they
    live on the cockpit. See Open Questions, that needs a decision from you.

---

## 2. Control inventory

Everything on `purchases.html`. "Clicks" counts taps to reach **and** apply,
from a fresh page load on desktop.

### Filters and sorting (the actual subject)

| Control | What it does | Where it lives | Clicks | Frequency |
|---|---|---|---|---|
| Search box | Free text over reg + vehicle name only | Nav bar, always visible | 0 clicks, type only | Every session |
| Platform tabs (All / Motorway / Carwow / Auction4Cars / Dealer Auction) | Filters to one source | Section head, left | 1 | Every session |
| View buttons (Due in / In stock / Cancelled / All) | Filters by lifecycle state | Section head, after tabs | 1 | Every session |
| Bought date from | Lower bound on bought date | Inside Filters drawer | 2 (open, set) | Occasional |
| Bought date to | Upper bound on bought date | Inside Filters drawer | 2 (open, set) | Occasional |
| Column header sort | Sorts by that column, asc then desc then off | Table header row | 1 to 3 | Every session |
| Reset | Clears search + both dates **only** | Inside Filters drawer | 2 | Occasional |
| Saved views | Save/apply/delete a named filter+sort set | Views menu | 2 to apply | Rare today (0 saved) |

That is the **entire** filtering and sorting capability of this page. Four
filters, one sort mechanism, one preset system.

### Buttons in the same row that are not filters

| Control | What it does | Clicks | Frequency |
|---|---|---|---|
| Views | Saved view manager | 1 to open | Rare |
| Fields | Column show/hide, per table and per record | 1 to open | Rare, set once |
| Add car | Manual purchase modal | 1 | Rare |
| Stats | Rollup panel (count, all in, margin, chips, cancelled) | 1 | Occasional |
| Filters | Opens a drawer containing only the date range | 1 | Occasional |

Five buttons, none of which filter anything, occupying the row you scan for
filters. `Filters` is the worst offender: it is labelled with a filter icon,
opens a panel titled "Search and filter", and contains **no search and one
filter**.

### Everything else on the page

| Control | What it does | Where | Frequency |
|---|---|---|---|
| Page switcher (Live Auctions / Purchases / Under Offer / Lost) | Navigation | Nav bar | Every session |
| Sync now | Re-reads Motorway + Carwow purchases | Nav bar | Daily |
| Shortlisted cars | Deep link to cockpit shortlist | Nav bar | Occasional |
| Notifications bell | Session/health notices | Nav bar | Occasional |
| Settings | Settings page | Nav bar | Rare |
| Calendar | Disabled, "Coming soon" | Nav bar | Never |
| Row click (reg or vehicle) | Opens the record drawer | Table | Every session |
| Drawer tabs (Journey / Timeline / DealerKit) | Switches right panel | Drawer | Every session |
| Column drag / resize grips | Reorder and resize columns | Table header | Rare, set once |
| Photo / document thumbnails | Opens gallery | Table + drawer | Occasional |
| Inline cell editors | Edit status, video, person, paid, notes, dates | Table cells | Every session |
| Chip / Cancel / Check in | Record actions | Drawer | Occasional |
| Column footer rollups | Count, price, chips, paid totals | Table foot | Passive |

---

## 3. Click count audit, current state

Desktop, fresh load. **Bold** marks journeys that cannot be done at all.

| Journey | Clicks now | What adds the clicks |
|---|---|---|
| Land and see today's list | **0** | Default view is Due in. But see note below. |
| Filter to Motorway only | **1** | Clean. |
| Filter to Carwow only | **1** | Clean. |
| **Filter by price band** | **impossible** | No price filter exists on this page. Sort only. |
| **Filter by mileage** | **impossible** | No mileage filter exists anywhere in BidBrain. |
| **Filter by age or year** | **impossible** | Not on this page. Cockpit has a Year select (3 clicks). |
| Filter by make or model | **0 clicks, typing** | Search box is always visible. This one is genuinely good. |
| **Sort by margin** | **impossible** | Margin is not a column, it lives inside the Retail est. popover, so there is nothing to sort by. |
| Sort by price | **1** | Click Car price header. Undiscoverable but 1 click. |
| Sort by newest bought | **2** | First click sorts oldest first. Second reverses it. No way to say "newest" in one go. |
| Two filters plus a sort | **6** | Tab (1), Filters (2), from-date (3), to-date (4), close drawer (5), sort header (6). |
| Clear everything | **3 clicks, and it does not work** | Filters (1), Reset (2), close (3). Reset leaves platform, view and sort untouched. A true reset is 6 to 7 clicks. |
| Into a car and back out | **2** | Click row, click X. Filters survive. See the back button warning below. |

Notes on the two that look fine but are not:

- **Land and see the list, 0 clicks.** The Due in view sorts by soonest
  collection date. All 21 Due in cars currently have no collection date, so
  the sort has nothing to work with and the order is effectively arbitrary.
  Zero clicks to a list that is not actually ranked.
- **Into a car and back out, 2 clicks.** Correct with the mouse. But the
  drawer pushes no history entry, so on a phone the back gesture leaves the
  page entirely and you return to a fresh purchases page with every filter,
  the sort and the scroll position gone.

---

## 4. Consolidation options, ranked

### First, an honest disagreement

Your brief asks for the smallest possible number of visible filter and sort
controls. I would not aim there, because **this page is not over-filtered.**
Four filters for 213 records across a five stage lifecycle is lean already.
Removing filters would make it worse, not better.

What is actually wrong is a different shape of problem:

- five non-filter buttons occupy the row where filters should be
- the sort is completely invisible
- two platform tabs are permanently dead
- the one filter drawer that exists is nearly empty and mislabelled
- and on the screen you actually use, none of it is reachable

So the proposals below reduce the **visible control count** hard, but they do
it by moving non-filters out and by making the real filters and the sort more
discoverable, not by hiding filtering capability. Where I think fewer controls
would hurt, I say so under each option.

---

### Option A, "Tidy" (lowest risk)

Move the four non-filter buttons into one overflow menu, keep everything else.

**Merges or hides:** Views, Fields, Add car, Stats collapse into a single `⋯`
menu. Filters button stays. Platform tabs lose the two dead entries
(Auction4Cars, Dealer Auction) until a reader feeds them.

**Visible controls in the section head: 9 → 4** (platform tabs, view buttons,
Filters, `⋯`).

```
┌──────────────────────────────────────────────────────────────┐
│ [All][Motorway][Carwow]   Due in │ In stock │ Cancelled │ All │
│                                            [Filters ▾]  [⋯]  │
└──────────────────────────────────────────────────────────────┘
```

**New click counts:** unchanged for every filter journey. Stats and Fields
each gain 1 click (2 instead of 1). Sort unchanged and still invisible.

**Trade:** Stats goes from 1 click to 2. Acceptable, it is a read-only glance,
not an action. **This option does not fix the sort or the mobile problem**,
which is why it ranks last.

---

### Option B, "One filter surface"

Everything that narrows the list lives behind one control, with active
filters shown as removable chips. The sort moves into the same surface.

**Merges or hides:** Search, platform, view, bought date and sort all move
into a single `Filter & sort` panel. The section head keeps only that button
plus the `⋯` overflow. Active filters render as chips under the head.

```
┌──────────────────────────────────────────────────────────────┐
│  21 cars                          [Filter & sort ▾]     [⋯]  │
│  ⟨Due in ✕⟩ ⟨Motorway ✕⟩ ⟨Sorted: Car price ↓ ✕⟩             │
└──────────────────────────────────────────────────────────────┘
```

**Visible controls: 9 → 2** (plus chips, which are results not controls).

**New click counts:**

| Journey | Now | Option B |
|---|---|---|
| Land and see list | 0 | 0 |
| Motorway only | 1 | 2 (open panel, tap Motorway) |
| Make or model | type | type (search inside panel: 1 + type) |
| Sort by price | 1 | 2 |
| Two filters + sort | 6 | 4 |
| Clear everything | 3, broken | 1 (Clear all chips) |
| Into a car and back | 2 | 2 |

**Trade:** the two things you do most, switching platform and switching view,
go from 1 click to 2. That is the wrong direction for your most frequent
actions, and it is why this ranks second not first. It wins on "clear
everything" and on honesty (chips make active state visible), and it is the
best option for mobile because one button is trivially reachable.

---

### Option C, "Working list first" (aggressive, my pick)

Strip the default view to almost nothing by deciding what this page is **for**
and defaulting to it. The page is your working list of cars between winning
and arriving. So default to exactly that and show nothing else until asked.

**Merges or hides:**

- Views, Fields, Add car, Stats → one `⋯` overflow menu.
- View buttons (Due in / In stock / Cancelled / All) → a single count-bearing
  segmented control that collapses to one dropdown below 860px.
- Platform tabs → drop the two dead ones; the remaining three stay visible
  because they are one tap and used every session.
- Bought date → moves out of a dedicated drawer and into the same panel as
  everything else, because on its own it does not justify a button.
- Sort → becomes visible where it belongs: **on the column headers**, with a
  permanent faint arrow on hover and an active arrow when sorted, plus a
  "Sort" entry in the panel for touch users who cannot hover.
- Search → stays in the nav bar exactly as it is. It is the one control that
  is already perfect: zero clicks, always visible, and it covers make and
  model.

```
Desktop
┌──────────────────────────────────────────────────────────────┐
│ BidBrain    [Live Auctions][Purchases][Under Offer][Lost]     │
│             ⌕ Search reg, make or model     ⟳  ★  🔔  ⚙       │
├──────────────────────────────────────────────────────────────┤
│ Due in ▾  21 cars      [All][Motorway][Carwow]     [More ⋯]  │
├──────────────────────────────────────────────────────────────┤
│ REG ▾   VEHICLE   STATUS   CAR PRICE↓   RETAIL   DUE IN  ...  │
│ ...rows...                                                    │
└──────────────────────────────────────────────────────────────┘

Phone (≤860px)
┌────────────────────────────┐
│ ⌕ Search        ⟳  🔔  ⋯   │
├────────────────────────────┤
│ Due in ▾ 21    Motorway ▾  │
├────────────────────────────┤
│ ┌────────────────────────┐ │
│ │ SD66 XWV   BMW X1      │ │   ← card, not a 2,600px table
│ │ £7,500 · due 28/8      │ │
│ │ Video: awaiting  ● est │ │
│ └────────────────────────┘ │
└────────────────────────────┘
```

**Visible controls in the section head: 9 → 3.**

**New click counts:**

| Journey | Now | Option C |
|---|---|---|
| Land and see today's working list | 0 | **0** |
| Motorway only | 1 | **1** |
| Carwow only | 1 | **1** |
| Make or model | type | **type** |
| Bought date band | 2 | 2 (in `⋯` → Filter) |
| Sort by price | 1 (invisible) | **1** (visible arrow) |
| Sort by newest bought | 2 | **1** (dates default to newest first on first click) |
| Two filters + sort | 6 | **3** |
| Clear everything | 3, broken | **1** (a single Clear that clears all six pieces of state) |
| Into a car and back | 2 | **2** (and back button works) |

**What gets slower:** Stats, Fields, Add car and saved Views each gain one
click, into the `⋯` menu. All four are rare or set-once. Bought date stays at
2. That is the whole cost.

**Why this is the pick:** it is the only option that gets your two most
frequent actions to 1 click, makes the sort discoverable without adding a
control, and forces the mobile layout to be designed rather than shrunk. It
also kills the dead controls rather than hiding them.

---

### Ranking

1. **Option C, Working list first.** Best click counts on the journeys you
   actually run, fixes discoverability, forces the phone layout to be real.
2. **Option B, One filter surface.** Cleanest looking and best "clear all",
   but it taxes your two most frequent actions.
3. **Option A, Tidy.** Safe, quick, but leaves the sort invisible and does
   nothing for the phone, which is where your real pain is.

**A caution on the "one search box that does everything" idea.** It reads well
but I would not build it here. Typing `motorway due in over 5000` needs
parsing rules you must remember, gives no discoverability, and fails silently
when it misparses. Your search box already covers reg, make and model at zero
clicks, which is the 90% case. Spend the effort on the phone layout instead.

**A caution on "filters as sort weightings".** Do not. On a page about money
already committed, a soft ranking that quietly demotes rows rather than
excluding them is how a car goes unnoticed. Filters here should be exact.

---

## 5. Frustration report, ranked by daily pain

### 1. On a phone, nothing but the platform tabs is reachable
At 375px the page is 2,633px wide and the toolbar sits at x=1,876. Views,
Fields, Add car, Stats and Filters are all off screen. Scrolling right to
reach them drags the nav bar off with it (measured: nav `left` goes to -600
when you scroll 600px right). You said you use this one handed in the yard.
Today, on a phone, this page is a spreadsheet you drag around.
**Cause:** `.appshell{align-items:flex-start}` (render.py ~line 23) plus
`flex-direction:column` in `@media (max-width:860px)` (~line 78). In column
mode `align-items` governs the horizontal axis, so `.insetpanel` sizes to its
content (the 2,607px table) instead of the viewport. Forcing `stretch` in the
browser dropped overflow from 2,258px to 139px.

### 2. 86% of the money on the working list is provisional and it does not say so
18 of the 21 Due in rows are `payment_estimated`, meaning Motorway has not
confirmed the final figures. Car price, All in and Margin are rendered as
plain hard numbers. The only warning is an "Estimated breakdown" line **inside
the hover popover on the Auc Status column**, which is a different column and
invisible on touch. This is the most realistic way for you to misread a number
and act on it.

### 3. The Bought date filter silently hides 160 of 213 records
Any row with no `bought_date` never matches an active date filter, by design.
75% of your records have no bought date. Set a range and three quarters of
your history vanishes with no explanation. The empty state even says "No
purchases match this search or date range", which is technically true and
completely unhelpful.

### 4. The sort is invisible
Headers sort on click (`onSortHeaderClick`) but carry no arrow when unsorted,
no `title`, and no hover affordance beyond `cursor:pointer`. Measured:
`thHasSortIndicatorWhenUnsorted: false`, `thTitle: null`. There is no way to
discover that this page sorts at all.

### 5. Reset does not reset
`resetFilters()` clears search and both dates. It leaves the platform tab, the
view and the sort exactly as they were. The button lives in a panel titled
"Search and filter" and clears neither the search you can see nor most of what
is filtering your list.

### 6. Applying a saved view breaks the page navigation highlight
`applySavedView()` calls `document.querySelectorAll('.tab')`, which matches
both the platform tabs and the page switcher links (Live Auctions, Purchases,
Under Offer, Lost). Applying a view strips `on` from the Purchases tab, so the
nav stops showing where you are. **Verified live**: classes went from
`["tab","tab on","tab","tab"]` to `["tab","tab","tab","tab"]`.
This is the exact bug `setTab()` documents having already fixed for itself
("a bare '.tab' selector here would incorrectly clear its own on state too,
found live 2026-08-27"). The fix was never applied to `applySavedView`.

### 7. The back button loses everything
The record drawer pushes no history entry. On a phone the back gesture exits
the page instead of closing the drawer, and you come back to a reset page with
filters, sort and scroll position gone. There is no URL state and no session
restore, so a refresh costs the same.

### 8. Two of the five platform tabs can never show anything
Auction4Cars and Dealer Auction have no purchases reader
(`purchases_run.read_all()` only calls Motorway and Carwow). Measured
counts: Motorway 196, Carwow 17, the other two zero and permanently so. They
occupy 211px of your most cramped row.

### 9. The "Filters" button is mislabelled
Filter icon, panel titled "Search and filter", contains one date range and no
search. Everything you would look for in there is somewhere else.

### 10. The working list's default order is meaningless
Due in sorts by soonest collection date. All 21 Due in cars have no collection
date, so the tiebreak never fires and the order is arbitrary. You land on an
unranked list that looks ranked.

### 11. No staleness indicator anywhere
The page never says when the purchases data was last synced. No timestamp, no
`setInterval` refresh, no "as of" line. Leave the tab open all morning and it
silently serves you yesterday's picture. Session health notices exist but live
behind the bell on a different concern.

### 12. Keyboard and accessibility gaps
- Rows open via `<td class="opener" onclick=...>`. Not focusable, no `role`,
  no `tabindex`, no keyboard path. **You cannot open a car record without a
  mouse.**
- Sortable headers are `<th onclick>` with no `aria-sort` (measured: zero
  occurrences in the document) and no button semantics.
- Inline editors are reachable only by clicking the cell.
- Touch targets: control bar buttons measure 28 to 35px tall against the 44px
  minimum. The view buttons are 28px.

### 13. Empty states are generic
One message, "No purchases match this search or date range", regardless of
whether the cause was the view, the platform tab, the search or the dates. It
never names the filter responsible and offers no way to clear it.

### 14. Sync gives no completion summary
Sync now drives the shared progress bar, but when it finishes there is no
"3 new purchases, 2 updated" summary. The table repaints and you are left to
spot the difference.

---

## 6. Screen size findings

Measured on the live page with the real dataset.

| Width | Page scroll width | Horizontal overflow | Toolbar reachable |
|---|---|---|---|
| 375px | 2,633px | **2,258px** | No, at x=1,268 |
| 414px | 2,633px | **2,219px** | No, at x=1,868 |
| 768px | 2,649px | **1,881px** | No, at x=1,876 |
| 820px | 2,649px | **1,829px** | No, at x=1,876 |
| 900px | 900px | 0 | Yes |
| 1024px | 1,024px | 0 | Yes |
| 1440px | 1,440px | 0 | Yes |
| 1920px | 1,920px | 0 | Yes |

The cliff is at the 860px media query. Above it the layout is correct and the
table scrolls inside its own box. Below it the containment fails completely.

**375px and 414px (your phone, one handed).** Unusable as designed. 18
columns and a 2,607px table on a 375px screen. The nav bar scrolls away
horizontally with the content. Touch targets are 28 to 35px. The control
layout should not shrink, it should **change**: the table should become a card
list (reg, vehicle, price, due date, one status line), with platform and view
as two dropdowns and everything else behind one `⋯`. The Filters drawer
already goes full width below 760px, which is right, it is just unreachable.

**768px and 820px (tablet portrait).** Same failure, still below the 860px
breakpoint. This is the width most likely to catch you out because the page
looks like it should cope. Either fix the containment and let the table scroll
inside its box, or move the 860px breakpoint down so tablets keep the desktop
shell.

**1024px.** Correct. Table scrolls internally, toolbar visible at x=88. The
section head is tight: platform tabs plus five buttons in 936px. First
candidate for the `⋯` overflow.

**1440px.** Comfortable. Toolbar at x=659. No changes needed.

**1920px.** Fine, but the table is 2,607px against a 1,920px viewport, so you
still scroll sideways ~690px to reach DealerKit. Worth asking whether all 18
columns earn their place at any width, or whether the record drawer should own
the long tail.

---

## 7. Quick wins, under an hour each

1. **The 860px containment fix.** Add `align-items:stretch` (or
   `.insetpanel{width:100%}`) inside the `@media (max-width:860px)` block.
   One line, verified in the browser to cut overflow from 2,258px to 139px.
   Biggest single improvement available.
2. **Fix `applySavedView`'s selector.** Change `querySelectorAll('.tab')` to
   `querySelectorAll('#tabs .tab')`, exactly as `setTab()` already does.
   One line, kills a live bug.
3. **Make Reset actually reset.** Have `resetFilters()` also clear
   `currentTab`, `currentView` and `sortKey`/`sortDir`. It already knows about
   all of them, `currentCriteria()` lists them.
4. **Hide the two dead platform tabs** until a reader feeds them. Render them
   only when a platform has rows.
5. **Add a permanent faint sort arrow** to sortable headers, plus
   `title="Click to sort"` and `aria-sort`. Makes an existing feature findable.
6. **Move the estimated-payment warning next to the money.** A small amber dot
   on Car price when `payment_estimated`, rather than buried in the Auc Status
   popover.
7. **Rename the Filters button** to `Dates` (what it contains) or move the
   search into it so the label becomes true.
8. **Show the row count that the date filter excluded**: "21 shown, 160 hidden
   with no bought date" in the empty state and under an active date filter.
9. **Make rows keyboard reachable.** Give the opener cell
   `tabindex="0"` `role="button"` and an Enter handler.
10. **Add a last-synced timestamp** next to Sync now, from the newest
    `first_seen` in the data you already load.

---

## 8. Open questions

1. **Which page did you actually mean?** This matters most. Half your listed
   journeys do not exist on the purchases page: price band, mileage, age/year,
   make/model dropdowns and margin sorting. Those are cockpit
   (`cockpit.html`) capabilities. The purchases page is cars you have **already
   bought**, so "today's best opportunities" is not a concept it has. Did you
   want (a) this audit as written, (b) the same audit of the cockpit, or (c) do
   you believe the purchases page should gain those filters?
2. **There is no mileage filter anywhere in BidBrain**, on either page. Do you
   want one, and on which page?
3. **Margin is not a purchases column**, only a line inside the Retail est.
   popover, so it cannot be sorted. Should Margin become a real column?
4. **Is the Bought date filter worth keeping** given it silently excludes 75%
   of records? Would "last 30 / 90 days / all" presets serve you better than
   two date pickers?
5. **How much do you use In stock, Cancelled and All?** If Due in is 95% of
   your time, the view control could collapse to a single "show archived"
   toggle and save a whole segmented control.
6. **Which columns do you actually read on a phone?** I would default the
   phone card to reg, vehicle, car price, due in date and video status, but
   that is a guess about your morning.
7. **Do you want the drawer in browser history** so the back gesture closes it
   rather than leaving the page? It changes back-button behaviour everywhere,
   so it is your call.
8. Two of the numbers above (18 of 21 estimated, 160 of 213 with no bought
   date) reflect today's data. If a full purchases sync would fill those in,
   the pain ranking for issues 2 and 3 drops. Worth checking before you act on
   them.

---
---

# Part 2, the cockpit and cross page cohesion

Added 2026-08-28 after confirming that Live Auctions (`cockpit.html`,
`render_page()`, render.py line 8701) is the page holding the journeys the
original brief described. Measured live against the real 186 shortlisted cars.

## 9. Executive summary, part 2

1. The cockpit is where the clutter you sensed actually is. It carries
   **19 control instances** against the purchases page's 9.
2. The reason is repetition, not richness. Sort, Cazana price, Ending soonest
   and Filters are **rendered once per platform section**, three times over,
   all driving the same global state.
3. There are **three separate sort mechanisms** competing for one job, and one
   of them, the Sort dropdown, is also secretly the view filter.
4. The cockpit's own `resetFilters()` is genuinely good, far better than the
   purchases one. It clears sixteen things. It misses only the platform tab.
5. Mobile is far healthier than purchases: 179px overflow rather than 2,258px,
   because cards reflow and a table does not. But cards render **514px wide on
   a 375px screen**, so it still does not fit.
6. Between the two pages, only **one** control is genuinely consistent: the
   search box. Everything else expresses the same idea differently.
7. The word "View" means two unrelated things depending which page you are on,
   and "All" appears twice on one screen meaning two different things.
8. The biggest single win available is not visual. It is **shared filter state
   across the two pages**, so "Motorway BMWs" survives the jump.
9. Do the same three control frame on both pages. Nothing is removed, all five
   cockpit orderings and all eleven filter inputs survive, they just live in
   one menu that sits in the same place wherever you are.
10. Mockup: **One frame, two pages** (link at the end of section 12).

## 10. Cockpit control inventory

| Control | What it does | Where | Clicks | Frequency |
|---|---|---|---|---|
| Search | Reg, make, model | Nav bar | 0, type only | Every session |
| Platform tabs (6) | All / Motorway / Carwow / Auction4Cars / Dealer Auction / DealerWay | Section head | 1 | Every session |
| Sort and view select | 3 sorts (Best opportunity, Best match, Cheapest reserve) **plus** 4 views (All, Gap fillers, Auto only, Held back) in one dropdown | Every section head, ×3 | 2 | Every session |
| Cazana price sort | Standalone toggle, off → asc → desc, overrides the dropdown | Every section head, ×3 | 1 to 3 | Occasional |
| Ending soonest sort | Standalone toggle, same shape, mutually exclusive with the above | Every section head, ×3 | 1 to 3 | Occasional |
| Filters button | Opens the 11 input drawer | Every section head, ×3 | 1 | Every session |
| Make | Select | Filters drawer | 3 | Every session |
| Model | Select, rebuilt from Make | Filters drawer | 5 | Occasional |
| Year | Select | Filters drawer | 3 | Occasional |
| Transmission | Select | Filters drawer | 3 | Occasional |
| Body style | Select | Filters drawer | 3 | Rare |
| Ends within | Select, today to 7 days | Filters drawer | 3 | Occasional |
| Reserve min / max | Two number inputs | Filters drawer | 1 + typing | Occasional |
| Max bid min / max | Two number inputs | Filters drawer | 1 + typing | Occasional |
| Retail guide min / max | Two number inputs | Filters drawer | 1 + typing | Occasional |
| Reset | Clears 16 things, not the platform tab | Filters drawer | 2 | Occasional |
| Saved views + starred quick buttons | Presets | Filters drawer, quick buttons per section head | 1 to 2 | Rare |
| Per card: star, copy reg, flags, valuation detail, hide | Card actions | On each card | 1 | Every session |

**Visible instances measured on the live page: 19.** Six platform tabs, one
search, and four controls repeated across three section heads. The distinct
concepts number six.

## 11. Cockpit click counts

| Journey | Clicks | What adds them |
|---|---|---|
| Land and see today's best opportunities | **0** | Default sort is Best opportunity. Genuinely good. |
| Filter to one source | **1** | Clean. |
| Filter by price band | **1 + typing** | But **three** price axes exist (reserve, max bid, retail guide) and nothing says which one you want. |
| **Filter by mileage** | **impossible** | No mileage filter on either page. |
| Filter by age or year | **3** | Filters, open Year, pick. |
| Filter by make | **3** | Filters, open Make, pick. |
| Filter by make and model | **5** | Model only populates after Make is chosen. |
| Sort by margin | **0 or 2** | Best opportunity is the margin proxy and is the default. |
| Sort by price | **1** | Cazana price toggle. |
| Sort by ending soonest | **1** | Its own toggle. |
| **Sort by newest listing** | **impossible** | No listing date sort exists. Ends within is a filter, ending soonest is time remaining, neither is "newest". |
| Two filters plus a sort | **8** | Filters (1), Make (2, 3), Year (4, 5), close (6), sort (7, 8). |
| Clear everything | **3** | Filters, Reset, close. Leaves the platform tab set. |
| Into a car and back | **leaves the app** | Cards link out to Motorway or Carwow in a new tab. There is no in app detail view, unlike purchases. |

## 12. Cohesion, how the two pages should relate

### What is actually inconsistent today

| Concept | Live Auctions | Purchases | Verdict |
|---|---|---|---|
| Search | Nav bar, always visible, live | Nav bar, always visible, live | **Already right, do not touch** |
| Source | 6 tabs | 5 tabs, 2 permanently empty | Same idea, different lists |
| Which slice of the list | Hidden inside the Sort dropdown as an optgroup | Four segmented buttons | Same idea, unrecognisably different |
| Ordering | 3 competing visible controls | Invisible column header clicks | Same idea, opposite extremes |
| Attribute filters | 11 inputs | 1 input | Purchases has none of make/model/year despite holding the data |
| Reset | Clears 16, misses platform | Clears 3 of 6 | Different contracts |
| Presets | Views menu + starred quick buttons | Views menu, no quick buttons | Nearly aligned |
| Repetition | ×3, once per section | ×1 | Cockpit triples its own surface |

Two collisions worth calling out on their own:

- **"View" means two different things.** On the cockpit it is a category
  (Gap fillers, Auto only, Held back). On purchases it is a lifecycle stage
  (Due in, In stock, Cancelled). Same word, unrelated meanings, and on the
  cockpit it is buried in a control labelled Sort.
- **"All" appears twice on one screen.** Once in the platform tabs meaning
  every source, once in the view control meaning every category. On the
  cockpit both are visible simultaneously.

### The proposal, one frame on both pages

Both pages do the same job: narrow a list of cars, order it, open one. So make
the frame identical and let only the **contents** of the menu differ.

```
┌────────────────────────────────────────────────────────────────┐
│ BidBrain  [Live Auctions][Purchases]     ⌕ Search      ⟳ 🔔 ⚙  │
├────────────────────────────────────────────────────────────────┤
│  ‹slice ▾› 15    [All][Motorway][Carwow]          [⋯ More  2]  │
├────────────────────────────────────────────────────────────────┤
│  FILTERING  ⟨Motorway ✕⟩ ⟨BMW ✕⟩                    Clear all  │
├────────────────────────────────────────────────────────────────┤
│  …cards on Live Auctions, table or cards on Purchases…         │
└────────────────────────────────────────────────────────────────┘
```

Three controls, in the same place, on both pages:

1. **Slice picker.** Page specific vocabulary, identical component. Cockpit:
   All / Gap fillers / Auto only / Held back. Purchases: Due in / In stock /
   Cancelled / All. This pulls the cockpit's view out of the Sort dropdown,
   where it never belonged.
2. **Source tabs.** Only platforms that actually hold cars. That alone removes
   two dead tabs from purchases.
3. **⋯ More.** One menu holding Sort, then Filters split into two labelled
   groups, then Views, then Clear all. The badge shows how many filters are
   live so you can see state without opening it.

Inside that menu, the filters are grouped by whether they travel:

- **Shared** (identical on both pages, and they persist across the jump):
  Make, Model, Year, Mileage. Plus source and search, which are already in the
  frame. Purchases holds `name`, `mileage` and a reg you can read a year from,
  so all four work there today with no new data.
- **This page only:** cockpit gets Transmission, Body style, Ends within and
  the three price ranges. Purchases gets Bought date, Car price and Retail est.

### The part that actually pays for itself

**Shared filter state across the two pages.** Set Motorway and BMW on Live
Auctions, tap Purchases, and you are still on Motorway BMWs, now showing what
you already bought. "What am I about to buy" and "what did I already buy" is
one question about one make, and today you type it twice.

Page specific filters are **dropped and named** on the jump, never silently
carried or silently ignored, because "Ends within: today" is meaningless on a
list of cars you already own.

### What this does to the numbers

| | Live Auctions now | Purchases now | Both, one frame |
|---|---|---|---|
| Visible control instances | 19 | 9 | **3** plus search |
| Sort mechanisms | 3 | 1, invisible | 1, visible, same place |
| Filter capability | 11 inputs | 1 input | **12**, nothing lost, 4 gained on purchases |
| Source filter | 1 click | 1 click | 1 click |
| Slice filter | 2 clicks | 1 click | 1 click |
| Clear everything | 3, misses platform | 3, misses half | **1**, clears all |
| Carrying a filter between pages | not possible | not possible | **automatic** |

Nothing is removed in capability terms. All five cockpit orderings survive,
all eleven filter inputs survive, and purchases **gains** make, model, year and
mileage, which it should have had all along.

### Honest costs

- Slice moves from 4 always visible buttons to a dropdown on purchases: still
  1 click to open, 2 to change. A small loss on the page's most used control,
  bought back by the frame being identical everywhere.
- Cockpit's Cazana price and Ending soonest go from 1 click to 2, into the Sort
  menu. They are occasional, and today they are three duplicated pairs of
  buttons for two orderings.
- Shared state needs somewhere to live (a URL parameter or one settings row).
  It has to survive a page load, or the whole idea fails.

### Mockup

**One frame, two pages** —
https://claude.ai/code/artifact/dbd80725-c441-48eb-b6d1-fedbe7ff1aea

Real data on both sides, 15 live auction cars and your 21 Due in purchases.
Switch pages with the tabs inside the mock: set a source or a make on Live
Auctions, jump to Purchases, and watch the chips survive. Desktop and 375px
phone both included.

## 13. Additional open questions, part 2

1. **Is a listing date sort worth adding?** "Newest listing" does not exist on
   either page. Ends within and Ending soonest are about time remaining, not
   about what appeared today. Do you ever want "what is new since yesterday"?
2. **Which of the three price axes do you actually filter on?** Reserve, max
   bid and retail guide are three separate range pairs. If you only ever use
   one, the other two are noise.
3. **Should the cockpit's Held back cars be a slice or a filter?** Today they
   are a view option, but they are really "cars the brain excluded", which is
   closer to a status than a category.
4. **Where should shared filter state live?** A URL query string makes it
   linkable and survives reload for free. A settings row makes it sticky across
   devices but invisible. My preference is the URL.
5. **Do you want the cockpit's card detail in an in app drawer**, like
   purchases has, rather than only linking out to the platform? It would make
   the two pages behave the same on click, which is the last real asymmetry.
