Blog · 13 Sep 2026

Building Max Bid: a limit before the bid

How I built a small resale-margin calculator to make auction decisions with the full cost in view.

Max Bid resale margin calculator banner

I like small software projects that end with a clear answer. Max Bid started with a practical question: when an item looks cheap at auction, what is the most I can pay and still make the profit I planned?

It is easy to look at the purchase price and treat the difference as profit. Resale has other costs: auction commission, platform fees, shipping, packaging and the profit you want to protect. Leave one of them out and a bargain can turn into a loss.

Max Bid is the first product I published under Mirtilo IO. I chose it as a small first experiment with a concrete use case, a simple explanation and a result that someone can check for themselves.

A calculator for one decision

The scope is deliberately narrow. Max Bid does not place bids or connect to marketplaces. It answers one question: what is the highest price I can pay for an item and still reach my target profit after the sale?

The calculation uses seven values:

  • expected selling price
  • purchase cost
  • shipping cost
  • packaging cost
  • auction commission
  • platform fee
  • target profit

The formula works backward from the expected sale:

maximum bid =
  selling price
  − auction commission
  − platform fee
  − shipping
  − packaging
  − target profit

Suppose I expect to sell an item for €200.00. The auction commission is 10%, the platform fee is €20.00, shipping is €10.00, packaging is free, and I want to keep €50.00 in profit. The maximum bid is €100.00.

If the purchase cost is €80.00, the projected profit is €70.00 and the result is Buy it. If the auction price goes above €100.00, the result becomes Don’t buy. The calculator gives the same answer as the formula, without asking me to rebuild it in a spreadsheet every time.

The number needs to be explainable

A calculator that shows one large number asks the user to trust it. I wanted the opposite. Max Bid shows the recommended maximum bid first and then lays out the expected sale, commission, platform fee, shipping, packaging, purchase cost, target profit, total costs and projected profit.

The implementation works with cent-accurate monetary arithmetic and consistent half-up rounding. A purchase cost equal to the maximum bid still qualifies as Buy it. A higher purchase cost results in Don’t buy. These boundaries are small details, but they are exactly the details that make a financial calculation feel dependable.

The calculator also keeps its limits visible. It is a planning aid, not financial, tax, legal or marketplace-policy advice. Taxes, returns, discounts and marketplace rules can change the real result, so those costs still need to be checked before bidding.

Two implementations, one contract

The project has two implementations. The Rails application is the reference for the formulas, copy, accessibility, security and tests. The TypeScript application preserves that behavior as a static site deployed on GitHub Pages at maxbid.mirtilo.io.

Writing the public implementation in TypeScript was a delivery decision. Vite compiles it into browser assets that GitHub Pages can serve directly. That gives Max Bid a custom domain without a long-running application server or a database for the public calculator. TypeScript also gives the project a typed boundary between the domain rules, locale handling and DOM UI. The language fits the way this version needs to be delivered.

Keeping the public version static made the product boundary clearer. After the page loads, the calculation runs in the browser. Values stay in memory and are not sent to the pageview analytics service. There is no account or calculation backend, no history and no stored resale data in the public calculator.

That simplicity is useful for the product and for the engineering work. The browser-only version has fewer moving parts to operate, while the Rails application gives me a reference implementation and a place to exercise the behavior with a fuller test stack. The public site still has to match the reference in the things a user can see and use.

Small application, serious engineering

The user sees seven fields and a result. The repository carries much more context. Product intent is recorded in the README, the Speckit documents, the canonical content contract in src/content.ts, the security and topology guides, the test strategy and the release-evidence record. Together they describe the formulas, edge cases, browser and accessibility expectations, hosting constraints, privacy boundary and rollback procedure.

Those documents also make small behavior decisions explicit. A purchase cost equal to the maximum bid qualifies as Buy it. An invalid submission cannot leave an old recommendation on screen. Currency selection changes the unit and formatting but never performs a conversion. Writing these decisions down gives the implementation and the tests something precise to protect.

Security is part of the same design. The TypeScript application validates input at the calculation boundary, allowlists languages and currencies, renders user-controlled values as text, keeps calculator data in memory and excludes it from analytics, URLs and external APIs. The production artifact has no source maps, and the artifact check scans generated output for secrets and unsafe content. The Rails reference adds CSRF protection, a Content Security Policy, HTTPS enforcement, strong parameters and parameter filtering. Brakeman, dependency auditing and the release checklist cover the parts that a browser-only test cannot see.

The CI and CD workflow is also part of the product. On a push or pull request that changes typescript/, GitHub Actions checks out the code without persisted credentials, installs the locked Node.js 22 dependencies with npm ci, installs the Playwright browsers, and runs type checking, linting, unit and DOM tests, end-to-end tests, accessibility tests, the production build, the artifact check, the security scan and the high-severity dependency audit. CI uses read-only repository permissions, cancels obsolete runs and pins its third-party actions to commit SHAs.

The deployment workflow waits for a successful CI run from a push to main. It checks out the exact commit that CI evaluated, installs the locked dependencies again, rebuilds and inspects the production artifact, then configures GitHub Pages, uploads typescript/dist and deploys it. Only the deployment job receives Pages write and OIDC token permissions. A pull request can exercise the full quality gate without publishing anything, while a successful push follows a repeatable path from commit to the custom domain.

Localization is part of the product

Max Bid is available in English and Brazilian Portuguese. English supports EUR and US Dollar choices. Portuguese supports EUR and Brazilian Real. Changing the currency changes the unit and formatting; it does not apply an exchange rate, so every value in a calculation must use the same currency.

The number format matters just as much as the words. Portuguese users can enter values such as 1.200,50, while the English interface displays the familiar 1,200.50 format. The parser also accepts unambiguous European comma-decimal values so a valid number does not become a surprise rejection.

This was a good reminder that localization is a product decision. A translated label is only one part of the experience. The examples, input masks, currency choices, validation messages and result breakdown all need to make sense together.

Deciding what stays out

The current Max Bid has no accounts, payment flow, exports, uploads, platform comparison, affiliate links or calculation-value telemetry. Those features could become useful hypotheses later, but adding them now would change the privacy boundary and the amount of product the tool needs to support.

For now, keeping the calculator free and small has a clear purpose. It gives Mirtilo IO a real product to publish, maintain and measure. It also gives me a public example of how I approach software: start with a concrete decision, make the rules visible, test the edges and keep the architecture proportional to the problem.

Max Bid is live at maxbid.mirtilo.io. It is a small tool, but it represents an important first step for Mirtilo IO and a useful foundation for the products that follow.

Back to the blog