Selected work

TruPro · 01 / 05 · 2026

A Lead was never an entity. It was a job that hadn’t started yet.

A CRM, estimating, and field platform for roofing and exteriors. I’m the sole designer and developer since July 2026, across three products on one design system.

A TruPro job at the Approved milestone: identity bar with contract value and assignee, the milestone ladder from Lead to Closed, tab counts, and contact, location and assignment cards
Every job, from first call to paid, is one page with one milestone ladder. A lead is this same page at its first stage.
25/25of the incumbent’s automation rules rebuilt and seeded into every org
8,317lines deleted by three corrections to the data model
195design tokens and 29 components, one system across three products
464tests that enforce the design rules, contrast included

These are results of the build, not outcomes. TruPro is pre-release: no outside users, no revenue. Counts come from the product repository on 17 Sep 2026. The 25 rules are the incumbent’s rules as recorded on 30 Jul 2026.

Role
Product designer and developer, design partner to the founder
Timeline
Apr 2026 – present
Scope
CRM, siding visualizer, sales tools. UX through the schema
Status
End-to-end testing with in-house users

The problem

The incumbent’s nouns were states, not things

Every roofing CRM copies the incumbent’s vocabulary: Lead, Job, Estimate, Schedule Entry. TruPro started there too, because it was the only workflow anyone had written down. Each noun turned out to be the same job in a different state, stored as a separate copy that had to be kept in sync. One commit found a whole feature set running on a table that was permanently empty, with every test green.

Decision 01

A lead is a job at the first milestone

The lead list was already querying the jobs table, filtered to one milestone. I deleted the lead entity. Scoring, nurture, routing, aging and funnel analytics now point at jobs. The job page at the top of this page is the same page a lead lives on.

Three tables and two route trees removed. One PR deleted 7,177 lines.

Decision 02

A schedule entry is a labor order with dates

The board used to read its own scheduling table, with a backlog flag that had to agree with the orders. Now every bar on the board is a labor order. A job sits in the unscheduled tray because it has no dates, not because a flag says so.

The flag and its code are gone, and the board can’t disagree with the orders.

The production scheduler: crews grouped by trade, with job bars across the week and a To be scheduled tray
Each bar on the scheduler is a labor order.
A materials and labor order being built inside a job: line items with quantity, unit and price, then schedule and review steps
The order is built inside the job, then scheduled. The board reads it directly.

Decision 03

An estimate’s status is computed, never stored

The estimate table has no status column. A decision record banned it before the table shipped, and the rule has held for four months. Draft, Built, Sent and Signed are computed from what actually happened to the estimate.

Once an estimate is sent, it locks, even though it isn’t signed. That’s less convenient for the rep, the only person who logs in. But the homeowner is holding that number. Reopening a sent estimate is allowed, and it voids the signature packet.

The status can’t drift from what happened, and no price changes after the homeowner has seen it.

A signed vinyl siding estimate with a Reopen button, a 30 percent section margin, and material lines with cost per unit, cost, quantity and price
Signed, and locked. Changing it means reopening it.
An approved worksheet with a banner explaining that only the waste percentage can still change, and it never changes the approved price
After approval, only waste can change. It moves the order quantity, never the approved price.

Decision 04

The usability test runs inside the product

I’m the only designer and developer on three products, with no research team to hand questions to. So I built the research tool into the product. It’s a panel docked in the running app. Each task describes a situation and what done looks like, never which button to press. Testers mark each task Done, Struggled, Stuck or Skip, because pass/fail throws away the cost of getting there.

In the first recorded session, 2 of 10 tasks were done unaided. That session logged 21 issues, and they were triaged into 19 specced findings.

The system

Design rules written as tests

One design system runs the CRM, the siding visualizer and the sales tools. Its rules are tests, not guidelines. No raw hex outside the token layer. Contrast is checked against the real token values. Every wrapped control has exactly one focus ring. Sticky headers can’t scroll away from their tables. When a feature push rebuilt all 25 automation rules across 24 files, it added zero tokens and zero components.

The dashboard: current pipeline by milestone, work schedule, accounts receivable aging, and the team's activity feed
The dashboard: pipeline by milestone, receivables aging, and the team’s activity feed.

More of the platform

Contacts through invoicing

Reflection

What I’d do differently

  • Get the client’s intended future workflow before modeling anything. I modeled on the incumbent’s vocabulary while the real workflow was still being decided.
  • Put every design rule under a test from day one. The rule with a test held. Inline styles had no test, and they drifted.
  • Record test sessions centrally. The research tool keeps results in the tester’s browser, so only one session is on record.

Pre-release: no outside users, revenue or outcomes. Screenshots come from a seeded demo organization, so every name and figure in them is invented.