Proposify Proposal Editor, Lead Product Designer.
When I joined, V3 was the future of the product and barely a document editor. Text and images worked; almost nothing else did. Tables couldn't do real work and nothing let you reuse content, so customers rebuilt the same proposal by hand every time. People tried V3, hit a wall, and went back to V2, a platform that had already reached its technical limits.
To find them, I ran a standing Thursday session with one to five of the editor's heaviest users, for as long as I was on the product. Every week followed the same loop:
Richer block interactions and a real pricing table were first judged too large for a V3 MVP, and the concern was fair. But the interview record showed the same limitations week after week, from the same accounts, including the ones reverting to V2. That turned my opinion into a pattern with names attached.
With the PM and tech lead, I split the work into phases around one rule: build the interaction model first, so every block and feature added later would inherit it instead of becoming another special case.
+ Add block
| Aa Text | $ Currency | x Multiplier | $ Subtotal | |
|---|---|---|---|---|
| Item Name | Price | Qty | Subtotal | |
| ⋮⋮ | Implementation One-time | $18,000 | 1 | $18,000 |
| ⋮⋮ | Premium Support Recurring, annual | $2,400 | 1 | $2,400/yr |
| ⋮⋮ | Onsite Training Optional, click to add | $1,200 | 1 | $1,200 |
| After 10% discount | Subtotal $18,360 | |||
Try it: toggle the optional line item
It treated every block the same: copy the markup, paste the markup. That worked for text, headings and images, which is why it reached QA. But a pricing block holds live formulas and variable bindings, so a pasted copy looked identical and was quietly connected to nothing. QA caught it before customers did, and we traded the simple generic handler for per-block copy behaviour, where each block declares what duplicating it means. More work, and the version that shipped.
Simple and general. Treats a binding as if it were decoration.
Each block type declares how it duplicates. A block isn't markup; it's a binding, and the editor had to know the difference.
The pricing and editor architecture established here became part of the foundation for CPQ.