Skip to content

MVP Build Order & Product Design Principles

Status: agreed for MVP · Last updated: 10 June 2026

Companion to: Architecture, Schema & Cost Model.

Original strategy doc: “MVP Build Plan - Quoting, Invoicing & Tax App for Tradespeople”.

Getting paid is the acquisition hook; tax is the retention hook. Build money-in first, tax second, MTD filing third. Every feature passes the test: would our customer zero actually use this on a Tuesday in a van?

Before code (this week)

Shadow customer zero through one real quoting cycle and one late-payment chase; design screens to mirror his existing steps. Register on the HMRC Developer Hub now - recognition is the long pole for Phase 3 even though the API isn’t touched for months.

Bootstrap weekend

Monorepo: /app (Expo, TS), /web (client quote page), /api (Python Azure Functions). Supabase project created from version-controlled migration files (supabase db push), never dashboard clicking. Empty Function App deployed so the pipeline exists before it matters.

Work split: one of us on Expo/web, one on Functions/database, meeting at the Supabase schema and a small set of API contracts.

Weeks 1-3 - the vertical slice

Create quote in app -> public token link -> client opens web page -> taps accept -> status flips -> push notification fires. No payments, no PDF, no expenses. Ugly, end-to-end, on customer zero’s actual phone.

Week 4 - money

Stripe Payment Link on the accept page for the deposit; webhook -> payment row -> invoice paid. This is the quote-to-paid demo shown to the 5-10 trades customer zero introduces.

After the slice

PDF generation, receipt capture, chase messages - in that order. None matter until quotes flow.

Scope discipline: anything that smells like a settings page, “manage X” screen, or job management goes in a parked-ideas file, not the backlog. The vertical slice is the only thing with permission to exist for a month.

  • pdf.py - WeasyPrint quote/invoice render
  • receipts.py - Claude vision call -> {amount, date, merchant, category}
  • webhooks.py - Stripe signature verify, idempotent payment insert
  • chasers.py - daily 08:00 timer, derived-overdue query, email -> SMS ladder
  • Deposit-link endpoint - on accept: create deposit invoice + Stripe Payment Link (holds the Stripe key, hence Functions not Postgres)
  • One-thumb, in-a-van: primary action is a full-width button at the bottom of every screen; targets big enough for gloves.
  • Plain English, never accounting jargon: “Owed to you”, not “accounts receivable”; “Put aside for January”, not “estimated liability”.
  • “Owed to you” is the first number on the home screen, with the overdue amount as a red badge - the daily reason to open the app. Always show post-CIS figures: what actually lands in the bank.
  • CIS is always framed as “tax already paid”, never “deduction”. The progress bar (“55% of your January bill already covered”) is the emotional core of the tax feature and the word-of-mouth moment.
  • Invoice detail shows the contractor’s maths explicitly: total -> CIS withheld -> you’ll receive.
  • Automation with a manual override: chasers run nightly, but “chase again now” and “mark paid another way” keep the user in control. Automation that can’t be overridden gets turned off.
  • Receipt capture is confirm-not-type: extraction pre-fills, user checks and saves. Category chips (six, on-screen), not dropdowns. Camera -> glance -> save in under ten seconds.
  • Rate picker is a bottom sheet asking a question (“How many days?”) with the live total on the add button. Per-job override = manual price, null rate card. More than roughly five rate cards is a signal the user is really a team, which is out of scope.
  • Status colours are a language: green = money/done, amber = waiting, red = overdue, grey = inert. No badge should need reading to know if it’s good news.
  • The client never logs in and never installs anything. Quote, accept, and pay all live on one tokenised link.
  • The tradesperson’s brand leads the page; ours stays out of the way. It’s Rhys’s quote.
  • Trust signals by the pay button: “handled by Stripe”, “Rhys never sees your card details”. Deposit/remainder split stated before acceptance - no surprises.
  • The accept tap and the deposit ask are one gesture, not an invoice email three days later.
  • Expired quotes are not dead ends: one-tap “ask for an updated quote” notifies the tradesperson and turns a stale lead warm. Cheap to build, highly tellable.
  • Position as a running estimate, never advice. Keep the “estimate only - based on what’s recorded here” line.
  • The real calculation has traps: payments on account (doubles the first year’s bill - the thing that blindsides first-year subbies), Class 4 NI thresholds, trading allowance. Payments-on-account warning is a must-have before Phase 2 ships.
  • Accountant export ships as a dumb CSV (the five summary numbers + categorised expenses) long before MTD. Accountants tolerate it happily; it buys Phase 3 time.
  • Can’t compete on compliance price (ANNA et al. file free); we win on the quote-to-paid workflow.
  • HMRC recognition drags - sandbox work starts months early.
  • Scope creep into job management - resist until people pay for it.

A scratchpad for ideas worth considering after the vertical slice. Anyone can add — keep entries short, lead with the user problem, not the implementation. Promotion into the backlog happens during build-order reviews; until then, nothing here is committed.

Format per entry: Title — one-line user problem. Added by, date. Optional second line for context.

  • Similar past jobs at quote time — while drafting a quote, surface previous jobs that look similar so the tradesperson can see what they charged before. Avoids under-pricing and speeds up quoting; works off existing quotes / quote_items data.
  • Invisible discount / mark-up — let the tradesperson apply a private adjustment (e.g. mates’ rates for family/friends, or a mark-up for awkward jobs) that never appears on the client-facing quote or PDF. Client sees only the final line total; internal records keep the original and the adjustment for the tax estimate.