Engineering
Why we never sum a total from the rows we render
By Jahangir Alam · September 16, 2026 · 6 min read
If your UI can hide a line, your totals cannot be derived from the lines it shows. This sounds obvious written down. It is not obvious while you are writing the component, and it is the root of a whole family of bugs where a system leaks the exact number it was asked to conceal.
This is a post about two features that look unrelated until they meet, the invariant that keeps them honest, and the place we still got it wrong.
The setup
We build a B2B quoting app for Shopify. Two capabilities interact in a way neither of them anticipates:
- Per-line price visibility. A merchant can mark a quote line as price hidden - the buyer sees the item with "Included" instead of a figure. Freight being absorbed, a sample, a service folded into another line.
- Partial acceptance. A buyer can accept some lines of a proposal, decline others, and leave the rest open.
Both change which rows a buyer sees. Neither is allowed to change what the buyer owes.
The bug you will write
The natural implementation of a totals block is a reduce over the rows you're rendering:
const subtotal = visibleLines.reduce((acc, l) => acc + l.lineTotal, 0)
Now hide a $500 line. The subtotal silently drops by $500, and the buyer sees a total that doesn't match the quote they were sent. So you fix it the obvious way - sum all lines, render some:
const subtotal = allLines.reduce((acc, l) => acc + l.lineTotal, 0) // still wrong
Now the total is right and the line is hidden. And the buyer can recover the hidden price with one subtraction, because the gap between the visible rows and the printed total is the hidden number. You have built a puzzle, not a privacy feature.
The rule
Buyer-facing totals come from the stored quote total. Never from the rendered rows.
In our schema the authoritative figure is written when the proposal is sent. Every surface - the storefront drawer, the Shopify customer account extension, the hosted portal, four PDF templates - reads that stored value. No surface re-derives it.
That's necessary but not sufficient, because on its own it only moves the leak. If the stored total includes a hidden line's price, the subtraction still works. So the rule needs a partner constraint:
A price-hidden line must be zero, enforced before send.
Which is the honest version of the feature anyway. "Price hidden" can only mean this costs you nothing extra. It cannot mean we're not telling you what this costs, because a line that moves a visible total is not hidden - it's obfuscated, and buyers are good at arithmetic. We reject the proposal at send time rather than rendering something we'd have to apologise for.
Together the two constraints give a property worth stating as an invariant:
The visible rows always sum to the printed total. Not approximately. Exactly.
Then partial acceptance arrives and breaks it anyway
Here's what we got wrong, and it's the interesting half.
When a buyer accepts part of a quote, the Confirmation PDF should show the accepted lines. We scoped the line table, added an accepted-totals figure, wrote the tests, shipped it.
The document came out with a $1,400 table of accepted lines under a header reading CONFIRMED TOTAL $1,825.00.
The totals block had been updated. The hero - a separate component, rendered above it, built when every acceptance was all-or-nothing - had not. It was still reading the whole-quote total, correctly by its own lights, and contradicting the table three inches below it.
The invariant held in the place we were thinking about and broke in the place we weren't. A second reader of the same underlying fact, written months earlier, silently went stale.
The fix that generalises
Not "update the hero too". One function that every headline consumer must go through:
export function documentHeadlineTotal(quote: QuoteContext): {
label: "Accepted total" | "Confirmed total"
amount: string
} {
return quote.acceptedTotals
? { label: "Accepted total", amount: quote.acceptedTotals.subtotal }
: { label: "Confirmed total", amount: quote.version.totalEstimate }
}
The label and the amount are chosen together and returned together. You cannot pick up the accepted amount and keep the whole-quote label, because they arrive as one value. The class of bug is designed out rather than patched.
If you take one thing from this: when two things must agree, return them from the same function. Two call sites reading the same source will drift the moment one of them is edited.
What we'd tell another team
- Find every consumer of a derived number before you change what it means. We grepped the totals component and not the document hero. A "who renders a total?" inventory would have caught it in five minutes.
- Enforce at the boundary, not the render. The zero-price constraint is checked at send. By render time it is too late to do anything but lie or crash.
- Test the arithmetic, not the presence. Asserting "the accepted total is displayed" passes happily while the hero contradicts it. Assert that the visible rows sum to the printed figure.
- Rasterise your PDFs in tests. Text extraction told us the right strings were present. They were - in the wrong place, saying the wrong thing in combination. Only an image showed it.
We verified the final behaviour against a real quote: four lines at 1400 + 350 + 75 + 0 (that last one price-hidden), a stored total of 1825, a buyer who accepted two lines, and a Confirmation PDF headed Accepted total 1,400.00 over a table of exactly those two lines. Visible rows sum to printed total. That's the whole test.
Footnote for Shopify app developers
If you're building on the customer-account UI extension surface, one related trap. The 64 KB bundle cap pushes you to fetch your copy strings at runtime rather than bundling them, which is reasonable. But then a string key can legitimately be missing - and strings.someLabel.replace("{count}", n) throws a TypeError, which crashes the extension, which leaves the host's spinner up forever with no error shown to the buyer. Interpolate through a null-safe helper. Ask us how we know.
Where this shows up in the product
Both features described here are live. Per-line price visibility and partial acceptance are on the Professional plan and above - see negotiation and proposals for the merchant-facing version of the same mechanics, partial acceptance for what it means commercially, and pricing for which plan carries what.
Related articles
See how QuotWay handles this on your store.