Public: anyone can read this; the crew can change it.
Each deal carries the agent's commission rate, like 2.5%, and its page shows the commission it pays at closing, kept off the client timeline #145
Check · Review · Land · Live done — Landed and live in production.
Each deal can now carry the agent's commission rate, and its page shows the commission it pays at closing. The full `bun test` run passed (470 tests) apart from the stale-contract check below, and I re-ran `src/openapi.test.ts` after regenerating `contract/openapi.json` (it passes). The design lint (`scripts/lint-design.ts`) passes. `bun x tsc --noEmit` printed only the tsc help text, so no type c…
shipped production is serving it deploy d2eada7
Each deal can now carry the agent's commission rate, and its page shows the commission it pays at closing. The full bun test run passed (470 tests) apart from the stale-contract check below, and I re-ran src/openapi.test.ts after regenerating contract/openapi.json (it passes). The design lint (scripts/lint-design.ts) passes. bun x tsc --noEmit printed only the tsc help text, so no type check ran. The repo has no scripts/gate.ts or scripts/fast.ts, and docs/VISION.md isn't in this repo.
- Storage: a nullable
commission_ratecolumn, appended as the last migration insrc/db.ts. The CHECK is 0–1000 hundredths of a percent, so 2.5% is stored as 250. - Parsing:
rate()insrc/money.tstakes "2.5" or "2.5%" (0–10, at most two decimals). Anything else gives aninvalidresult on fieldcommission_rate, which the form turns into a 422 that keeps what was typed. A blank value clears the rate. - Amount:
commission()insrc/money.tsis the one place the dollars are rounded, andpercent()formats the rate. - Doors:
- The new-deal form and the deal's figures form take the rate. Its field sits under the sale price and earnest money.
- createDeal and updateDeal handle it.
- The API deal schema, the open and update endpoints, and the MCP open_deal and update_figures tools carry it.
- contract/openapi.json is regenerated.
- Sample deals: they carry rates of 2.5%, 3%, 2.75% and 2.5%.
- Deal page: it reads "Commission 2.5% · $11,250" when the price is set, and "Commission 2.5%" with "add the price to see the amount" when it isn't.
- Kept private: I added nothing to the timeline page or the reminders code. The MCP
client_timelinetool doesn't return the rate either. A test asserts the timeline output and every draft subject and body contain no "commission", rate or amount. - Tests:
src/commission.test.tscovers the parse and its refusals, the rounding, create and edit, the 422 keeping the typed value, the page text in all three states, and the timeline and drafts leaving it out. - Docs: the README has a new Commission paragraph.
- Wire format: the API and MCP read and write
commission_rateas the stored integer (250), while the HTML forms take percent text. I chose this so reading a deal and writing it back match. A JSON integer is hundredths and a string is a percent. contract.json: unchanged, because the rate is a field ofdeal.- Not done: I didn't add a terminal editor for the rate, matching how the other figures work there. I also didn't mention the rate in
docs/TECHNICAL.md.
To try it, open a deal with Commission rate "2.5%" and a price of 450000. Its page shows "Commission 2.5% · $11,250", and the Timeline link shows neither the rate nor the amount.
🤖 Generated with Claude Code
Clean. The rate is parsed at the boundary in money.ts and refused as a 422 that keeps what was typed. The amount is rounded once. The deal page, API, MCP, openapi and samples carry it. The timeline and drafts leave it out, and a test asserts that. No contract cell changed. Nit, not blocking: the JSON integer (hundredths) and form text (percent) differ, but both are documented.
Each deal can now carry the agent's commission rate, and its page shows the commission it pays at closing. The full
bun testrun passed (470 tests) apart from the stale-contract check below, and I re-ransrc/openapi.test.tsafter regeneratingcontract/openapi.json(it passes). The design lint (scripts/lint-design.ts) passes.bun x tsc --noEmitprinted only the tsc help text, so no type check ran. The repo has noscripts/gate.tsorscripts/fast.ts, anddocs/VISION.mdisn't in this repo.commission_ratecolumn, appended as the last migration insrc/db.ts. The CHECK is 0–1000 hundredths of a percent, so 2.5% is stored as 250.rate()insrc/money.tstakes "2.5" or "2.5%" (0–10, at most two decimals). Anything else gives aninvalidresult on fieldcommission_rate, which the form turns into a 422 that keeps what was typed. A blank value clears the rate.commission()insrc/money.tsis the one place the dollars are rounded, andpercent()formats the rate.- The new-deal form and the deal's figures form take the rate. Its field sits under the sale price and earnest money. -
createDealandupdateDealhandle it. - The API deal schema, the open and update endpoints, and the MCPopen_dealandupdate_figurestools carry it. -contract/openapi.jsonis regenerated.client_timelinetool doesn't return the rate either. A test asserts the timeline output and every draft subject and body contain no "commission", rate or amount.src/commission.test.tscovers the parse and its refusals, the rounding, create and edit, the 422 keeping the typed value, the page text in all three states, and the timeline and drafts leaving it out.commission_rateas the stored integer (250), while the HTML forms take percent text. I chose this so reading a deal and writing it back match. A JSON integer is hundredths and a string is a percent.contract.json: unchanged, because the rate is a field ofdeal.docs/TECHNICAL.md.To try it, open a deal with Commission rate "2.5%" and a price of 450000. Its page shows "Commission 2.5% · $11,250", and the Timeline link shows neither the rate nor the amount.
🤖 Generated with Claude Code
Closes #142