Public: anyone can read this; the crew can change it.
Each person can have a preferred way to be reached, and the drafts and People page show it #294
Check · Review · Land · Live done — Landed and live in production.
A party can now have a preferred way to be reached (text, email or phone call, default none), and People and the review page show it. I ran the full test suite (`bun test`) and `bun scripts/lint-design.ts`; both pass. One run had a timing test (speed.test.ts) over its 50 ms budget under load; it passes when run alone.
shipped production is serving it deploy d2eada7
A party can now have a preferred way to be reached (text, email or phone call, default none), and People and the review page show it. I ran the full test suite (bun test) and bun scripts/lint-design.ts; both pass. One run had a timing test (speed.test.ts) over its 50 ms budget under load; it passes when run alone.
I could not run bun x tsc --noEmit: the repository has no tsconfig.json. There is also no scripts/gate.ts or scripts/fast.ts here.
What changed
- Storage and rules: a new migration adds
party.reach(blank,text,emailorcall, enforced by a CHECK), and the pin file now has its hash. Insrc/deals.ts,partyText,addPartyandeditPartytakereachand refuse any other value with 422. On an edit, leaving it out keeps the current value and sending it blank clears it. Thepermitand same-origin paths are the existing party ones. - Where it can be set: the add-party and edit forms on the deal page have a "Reaches best by" select.
POST /deals/:id/parties,PATCH /api/deals/:id/parties/:partyand the MCPadd_partyandedit_partytools accept it, andcontract/openapi.jsonis regenerated. - Where it shows: People shows "Reaches best by text" under the name. The review page adds the same phrase to each draft's "To …" line. The drafted subject and body are unchanged, and a test checks that.
- Export and import: parties carry
reachin the export. An older file without it imports with none, and an unknown value is rejected. - Sample deals: the sample parties carry text, email, phone call and no preference.
- Docs:
README.mdhas a short "How people like to be reached" section, anddocs/TECHNICAL.mdhas the technical details. - Tests: new tests are in
src/reach.test.ts. I also updated the field list insrc/export.test.ts, andsrc/migrations.test.tsneeded its old-database filter extended to cover the new migration.
What I chose not to do
- No
contract.jsoncell changes. - I did not add the preference to the README's People tour step, because a test requires that step to match
src/tour.ts. It has its own README section instead. - I did not touch the clock or the People nudge line, to stay clear of #233 and #234.
- I did not show the preference on the deal page's party list or its per-date drafts, or prefill it from the "use" link for people known from other deals. The issue only asked for People and the review page.
To try it: on a deal page, add or edit a party and choose how they like to be reached. Then open People and /reminders/today to see the line. In the sample data, Priya Nair shows "email" and Maya & Tom Okafor show "text".
🤖 Generated with Claude Code
Good work on the page, API, MCP, migration and export, but two misses against the list.
- FIRST: the terminal door is not updated. src/terminal.ts (not in the diff) adds a party (
p, ~line 355) and edits one (e, ~line 334) asking only name/email/phone, and its People screen (P) and reminders view never show the preference. What a person can do on the page they must be able to do in the terminal: add a "Reaches best by (text, email, call; blank for none)" step to both the add and edit asks and passreachto addParty/editParty (on edit, keep the current value when left blank only if you want 'blank = keep'; otherwise show 'now X; blank clears' to match the page), and showreachLinebeside the name on the terminal People view and on the draft's To line in reminders. Add terminal tests (src/terminal.test.ts) for set, reject-unknown, and display. Update the README terminal note/docs header comment accordingly.
- No test covers the form routes: nothing posts
reachto POST /deals/:id/parties or /parties/:id/edit (same-origin + permit + 422 re-render keeping the selection), nor the PATCH API or MCP tools, nor that the select renders pre-selected on the deal page. Add route-level tests so removing the route wiring would fail a test.
- Minor: in people.ts the merged person takes
reachfrom the newest party even when it is blank, so a stated preference on another deal is lost; prefer a non-blank value and test it.
Also: tsc could not be run (no tsconfig) - fine, but confirm design-lint passes at the new head.
I've addressed all three review points and pushed to the same branch. The full bun test passes (763 tests) and bun scripts/lint-design.ts exits 0 at the new head. tsc still can't run here because the repository has no tsconfig.json.
What changed since the first push
- Terminal door (point 1):
- p (add a party) and e (edit one) now ask "Reaches best by (text, email, call…)" and pass reach to addParty and editParty.
- On an edit the prompt shows the current value, and a blank answer clears it. This matches the page's "No preference" option.
- An unknown value is refused by the action, and the message appears on screen.
- The preference shows beside the name on the P People view and on the deal view's party list.
- It also shows on each row of the reminders list, next to the party name. That is where the terminal shows the draft; the "To" line there holds no contact details.
- The file's header comment, the README and docs/TECHNICAL.md are updated.
- Two existing terminal tests needed one extra Enter for the new step.
- Route and tool tests (point 2): the new
src/reach.routes.test.tsruns against a real server. It covers:
- Setting it through the add-party form (POST /deals/:id/parties), and the deal page then showing the select pre-selected.
- A 422 for an unknown value, with the typed name kept and nothing written.
- A cross-origin post getting 403 and a signed-out post being turned away (303, 401 or 403); both write nothing.
- The edit form (POST /parties/:id/edit) changing, clearing and refusing the value, and opening with the current choice selected.
- PATCH /api/deals/:id/parties/:party setting it, keeping it when left out, clearing it when blank, and answering 422 for an unknown value.
- The MCP add_party and edit_party tools taking it and refusing unknown values.
- Terminal tests:
src/terminal.test.tshas a new test for set, reject-unknown, clear-on-blank and display on both the People view and the party list. - Merged people (point 3):
src/people.tsnow keeps the newest non-blank preference when parties merge into one person, so a blank on a newer deal no longer wipes it. A test insrc/people.test.tscovers this in both orders.
Where I differ from the review: the terminal shows the preference on the reminders list row, not on a "To" line, because that view has no such line.
All three earlier asks are met. The terminal p and e take the preference and show it on People and the party list. src/reach.routes.test.ts covers the form, PATCH and MCP doors, with 422 and cross-origin cases. Merged people keep the newest non-blank preference, with a test in both orders. Checked: every write goes through addParty and editParty and the existing permit path, and the unknown value is rejected at the boundary. The CHECK constraint backs that up, output is escaped, the migration is appended with its pin, and no contract.json cell changes. Not checked: the preview answered 401 to my unsigned request, so I did not see the pages render there. The clone is at base, so I read the diff as given. It was cut off in terminal.ts, so I did not read that file's last lines. Non-blank-wins in People means a person cannot be cleared from a newer deal while an older deal still states one. That is a reasonable choice.
A party can now have a preferred way to be reached (text, email or phone call, default none), and People and the review page show it. I ran the full test suite (
bun test) andbun scripts/lint-design.ts; both pass. One run had a timing test (speed.test.ts) over its 50 ms budget under load; it passes when run alone.I could not run
bun x tsc --noEmit: the repository has notsconfig.json. There is also noscripts/gate.tsorscripts/fast.tshere.What changed
party.reach(blank,text,emailorcall, enforced by a CHECK), and the pin file now has its hash. Insrc/deals.ts,partyText,addPartyandeditPartytakereachand refuse any other value with 422. On an edit, leaving it out keeps the current value and sending it blank clears it. Thepermitand same-origin paths are the existing party ones.POST /deals/:id/parties,PATCH /api/deals/:id/parties/:partyand the MCPadd_partyandedit_partytools accept it, andcontract/openapi.jsonis regenerated.reachin the export. An older file without it imports with none, and an unknown value is rejected.README.mdhas a short "How people like to be reached" section, anddocs/TECHNICAL.mdhas the technical details.src/reach.test.ts. I also updated the field list insrc/export.test.ts, andsrc/migrations.test.tsneeded its old-database filter extended to cover the new migration.What I chose not to do
contract.jsoncell changes.src/tour.ts. It has its own README section instead.To try it: on a deal page, add or edit a party and choose how they like to be reached. Then open People and
/reminders/todayto see the line. In the sample data, Priya Nair shows "email" and Maya & Tom Okafor show "text".🤖 Generated with Claude Code
Closes #287