Concept B · built from Fabian's spec · 2026-09-12

The unit of work
is the pile.

Nobody clears a garage one object at a time. You walk the room with the camera, you put the phone down, and later you sit and go through a stack of finished ads like a stack of mail.

Two jobs — shoot and sort. The machine owns everything between them, and you are not expected to be there while it works.

Fixture state. No model is called, no photograph leaves your device, no price is real, no notification is real. The wheels, Ray-Ban and mixer searches, comparables and confidences are copied from the app's own live runs of 22 August and 31 August; the golf clubs, the bowl and the drill are written, not captured, and the fixture file says which is which field by field.

D-624 Found MoneyD-615 The Ticket D-498 photos storedD-396 three bands D-622 two registersfixture data only
Open the prototype → The first concept, for comparison →

Best judged on your phone. On a desktop it draws itself inside a 390 × 844 device frame; the Prototype tab at the top right switches that, and everything else.

The two concepts, in one sentence each

Concept A · strp-7q2v

The screen follows the object.

  1. Photograph one thing, guided frame by frame
  2. Confirm what it is
  3. Pick a condition
  4. Tap the gate, and watch the press run for five minutes
  5. Read the price, approve, post
  6. Start again for the second thing

Six things means six five-minute waits, serially, with the phone in your hand for all of them.

Concept B · this one

The unit of work is the pile.

  1. Photograph everything, free-form, "next thing", "next thing"
  2. Put the phone down
  3. The machine identifies, prices and writes all of them, three at a time
  4. Come back to a stack of finished cards
  5. Sort: looks right / fix something / not now
  6. Post the approved ones through the same paste rail

Six things means one absence. The confirmations move to the finished card, where you can judge them against a price and an ad at the same time.

They are not in conflict about the data. The item lifecycle is identical, and each Sort card is "the screen follows the object" for one object. What changes is which moments you are present for. The genuine fork is Home, Shoot and Sort, and it is a fork about what the app is for: one object's life, or a household's Saturday.

The middle is the point, so the middle got the craft

"Put your phone down, we'll handle it" is the concept's most distinctive moment and the easiest thing in the world to render as one progress bar and a spinner. That would be a lie about what the backend does. There is no single piece of work to measure: there are six independent lookups, three of them in flight, the rest sitting on a deliberate delay, each finishing when it finishes.

Real and interactive

The Face

The default after you finish shooting. One Ticket per thing, each breathing on its own period and its own phase — six marks in sync would read as one thing with six parts. A colour legend, because colour is the only carrier of state on this screen. And a ticker that rotates one true sentence at a time from whichever run is actually moving. No bar, anywhere.

Real and interactive

The Floor

The opt-in detail view. One lane per thing, each reading its own event log on its own timer, so the lanes genuinely drift apart — different search text, different rates, different finishing moments. The lane's ground fill is the search progress; there is no second bar. A finished lane stamps, flips to ink and migrates up out of "running" into "finished".

Real and interactive

The wave bar

Three at a time, ninety seconds apart, shown rather than hidden — with the reason, and a live countdown on the next one. A person who can see the stagger has a true model of why the fourth thing has not started yet. Both constants live in one place and drive the server's delay, the ready-by estimate and this bar.

What is genuinely real, and what is a marked placeholder

Same rule as the first prototype: where the spec leaves a decision open, use a reasonable placeholder and say so on the surface rather than picking quietly. Every open decision carries a dashed OPEN badge naming who holds it, and the five that are yours are switchable live in the Prototype drawer.

Real and interactive
  • The gate. gateAfterVision() is the real pure function from spec 5.1, and every downstream state is a consequence of it. The bowl and the sunglasses are genuinely held by it and genuinely never spend a lookup.
  • Shoot. A free-form tray, not a frame grid. Real camera on a phone (stand-in drawings on a laptop), a real per-thing upload ring, chips that go to "waiting for signal" when you drop the signal from the drawer and catch up when it comes back.
  • Hold-to-talk. Real webkitSpeechRecognition where the browser has it, wired to a real gesture, landing in a real editable field. Where it does not exist the control says so and the text field beside it is always there.
  • The middle, described above. All of it.
  • Sort, all six card states from the fixture: identity hold, wrong-item hold, failed hold, priced, insufficient-and-bracketed, and photo-would-help. Three fixed buttons in three fixed places in every one.
  • Answering a hold really starts a run. Type what the bowl is, and it goes back into the machine, finishes on the same timer as everything else, and rejoins the stack.
  • The Fix sheet's three consequences. Condition or price re-derives and rewrites in place in seconds; a title change asks first, because that one costs five minutes; photos re-run the vision pass and re-run the gate.
  • Split, take-them-out and they're-fine on the wrong-item card, all three.
  • Post. The paste rail reused from the first prototype: six steps, real clipboard, real advance, and it moves to the next approved card when you say it is up.
  • Motion. Every lane, pulse, stamp, deal and card transition computes to nothing under Reduce Motion, which you can force from the drawer without changing a device setting.
Marked placeholder — somebody's decision
  • The five decisions in spec 16 that are yours. Not resolved. Each one is a switch in the drawer that really changes what the app does: one-at-a-time, spend-before-confirmation, push, post order, machine-guessed condition.
  • Every string. Auden owns the words. The pile-level gate sentence, the three condition-chip sentences, the ready-by line, the push text and the tally are written as reasonable stand-ins and flagged.
  • Home's structure. Three stack cards and a tally above the rows is a structural consequence of the concept; whether it reads as a hallway table or as a dashboard is Linnea's, and it is flagged on the screen.
  • The Shoot tray, the chips, "Next thing", the hold-to-talk control's loudness, the Sort card in its six states, the ready-by sentence — Linnea, spec 16 items 7 to 10.
  • Push disclosure and retention, the voice-recognition routing line, and the charge-before- confirmation rule for the paying cohort — Adisa, spec 16 items 12 to 14.
  • The split sibling's price. A split has never been run. Its fixture says so in the reason line rather than pretending to be a result.
  • Per-search result counts and pacing, on every lane. The stream events were never logged.

Three things worth arguing with

Places where building it surfaced something the spec did not have, or got wrong.

Item codes do not have a profile prefix

Spec 4.3 says the code is "assigned from the general profile's prefix" and worries about re-lettering it when the category resolves. It reads nextCode() one step off: the prefix comes from the owner id, not from the profile, so there is nothing to re-letter and the whole concern dissolves. The prototype uses the real shape (RA-12). The spec paragraph should be struck rather than implemented.

"k of n" cannot count down

Built literally, the Sort counter read 1 of 5, then 1 of 4, then 1 of 3 — telling a person who had just made three decisions that they had made no progress. It now counts through, and when an answered hold finishes and rejoins the stack, n grows rather than k jumping backwards. Small, and exactly the kind of thing the cohort this is for cannot absorb.

The raw query does not belong on the calm screen

The cleaning rule falls back to the raw model query when it cannot name a venue, which is right — it never invents a label. But a raw query truncated mid-phrase is the wrong thing to put on a screen whose entire job is to be glanceable from across a kitchen. The Floor shows the search line; the Face shows the stage, which is always a readable sentence. The spec treats both surfaces as the same material.

The five that are yours, and where to look

Open decisionHolderWhere it shows
Does "one at a time" still stand? (spec: "this decides the concept")LanceDrawer · adds a second entry on Home
Spending before you confirmLanceDrawer · changes Done-for-now and the Floor
Push notifications at allLanceDrawer · the ask, and the simulated banner
Post order: shot order or highest priceLanceDrawer · re-sorts Ready to post
Machine-guessed conditionLanceDrawer · every condition chip in Sort
Home: stacks above rows, without becoming a dashboardLinneaHome
The Shoot tray, the chips, "Next thing", the three fixed wordsLinneaShoot
The Sort card in six states, and the Fix sheetLinneaSort
The hold-to-talk control: present, and how quietLinneaShoot
The ready-by sentence and the countdownLinneaDone for now · the Face
Push endpoints as stored data; retention and deletionAdisaMore
Voice routing through Apple's recognition serviceAdisaShoot · under the control
Charging before confirmation, paying cohortAdisa + D-499Done for now · the reserved slot
Every string, and the condition-chip sentencesAudenEverywhere a marker appears
The word "pile"; whether Shoot / Sort / Post ever surface as wordsKahuaThroughout
Sequencing against the D-499 payment and onboarding specKwitondaNot touched by this pass

What production would need

Lifted from spec 12 so it is on one screen. All additive; no existing column changes meaning. This is a bigger build than the first concept's — one new table, six columns, five routes, a worker hook, a service worker and a push path, against the spine's two columns and one new route.

Migration 0023_piles.sql

  • piles — id, owner, started_at, closed_at, label
  • items + pile_id, pile_position, hold_json, sort_decision, sorted_at, condition_source
  • photo_plans.condition_guess_json
  • research_runs.auto_started
  • push_subscriptions, and users.push_declined_at
  • Plus the spine's items.rail_json and users.town if not already landed

Routes and the worker

  • POST /piles, and /state gains the pile block with server-derived counts
  • startResearch() extracted from researchRoute, called by the route and by visionRoute — one function, two callers, so the two paths cannot drift
  • gateAfterVision(), pure and unit-tested per confidence value
  • POST /items/:id/answer · POST /items/:id/split · PUT /items/:id/sort
  • delaySeconds at enqueue — no worker change, no in-flight counter
  • notifyIfPileComplete() in the worker, plus VAPID secrets and a Workers-compatible signer

Two things the prototype cannot prove and does not claim to. Whether three concurrent fourteen-search calls trip the Anthropic rate limit is unmeasured — the stagger constant is a guess and production staging has to test it with a real pile of six before the family sees it. And the push path can only be proven on a real home-screen install; the banner here is drawn by the prototype and says SIMULATED on it.