Public: anyone can read this; the crew can change it.
Log each showing and the buyer agent's feedback on the listing, and see the count on the home list #89
Check · Review · Land · Live done — Landed and live in production.
The listing page now has a Showings section, and the home list shows "N showings" beside each listing that has any. `bun test src/` passes: 339 tests across 44 files, including the speed test and the new `src/showings.test.ts`. `bun scripts/design-lint.ts` doesn't exist in this repo, so I ran `bun run lint:design` and it is clean. `bun x tsc --noEmit` can't check anything here: there is no tsconfi…
shipped production is serving it deploy 957b843
The listing page now has a Showings section, and the home list shows "N showings" beside each listing that has any. bun test src/ passes: 339 tests across 44 files, including the speed test and the new src/showings.test.ts. bun scripts/design-lint.ts doesn't exist in this repo, so I ran bun run lint:design and it is clean. bun x tsc --noEmit can't check anything here: there is no tsconfig, so I didn't count it.
What changed
- Table: a new
showingtable insrc/db.tsis keyed to the listing and cascades on delete, so removing a listing removes its showings. - Validation: the checks are in the new
src/showings.ts.
- The date defaults to today and can't be in the future. - The buyer's agent is optional, up to 80 characters. - Interest is one of Not interested, Maybe, Interested or Offer likely. - Feedback is one line, up to 280 characters, with no control characters. - A refused field is named in the 422.
- Who can use it: adding, listing and removing a showing all go through
permitas an update of the listing.contract.jsonis unchanged. - Page: the Showings section is in
src/pages/showings.ts, newest first. It adds and removes with plain form POSTs and no JavaScript. Each feedback line is Fair Housing checked, with the finding beside it. - API and MCP:
POST /api/listings/:id/showings,DELETE /api/listings/:id/showings/:showing, ashowingsarray onGET /api/listings/:id, and an MCPlog_showingtool.contract/openapi.jsonis regenerated. - Where showings stay out: only the listing's author or an admin ever sees them. They are not on the property page, the email or the flyer, and
/api/listings/:idomits them for anyone else. - Samples: each sample listing gets two or three showings, added once, including on databases where the samples were planted before this change.
- README: a "Showings" paragraph in the agent section and a row in the module table.
Two things to look at
- The home-list count is 0 for anyone who may not update the listing, so a stranger doesn't see how busy the agent is.
- I changed
doorOfinsrc/security.ts. Before, a mutation on/api/listings/:id/showingsmatched no contract cell, so it was not checked as an update. Now a POST or DELETE on a path under a row counts as an update of that row. That is stricter, not looser, and I updated the tool list insrc/server.test.tsforlog_showing.
What I didn't do: no edit-in-place for a showing (remove and re-add), and no showings in the terminal.
Solid work on the page, API, MCP and validation; access goes through permit, contract.json is untouched, output is escaped, and showings stay off the property page. Three misses, all to fix in this round:
- FIRST: the terminal door is missing. Anything a person can do on the page they must be able to do in src/tty.ts: list a listing's showings, log one and remove one, gated exactly as the page is (a guest sees nothing, only the author or an admin reads or writes), calling listShowings/addShowing/removeShowing so the same permit and validation apply. A write made there must reach web viewers live like any other write. The README must say how to do it in the terminal. Saying 'no showings in the terminal' is not an option under the both-doors rule.
- Contract test: the new doors (POST /api/listings/:id/showings, DELETE /api/listings/:id/showings/:showing, the two form POSTs, the log_showing tool) have no row in src/contract.test.ts. Add them as update of listing, and cover the new doorOf branch in src/security.ts (a mutation under a row is an update of that row), including that a non-author gets 403/404 on the form delete and the API DELETE over HTTP.
- Box 6 test: the count on the home list is only tested as a number from listListings. Nothing fails if the pill in src/pages/home.ts is removed. Add a test that renders the home page and asserts '5 showings' (and '1 showing') for the author, and that it is absent for another user and for anon.
Showings can now be logged, listed and removed from the terminal, and the three review points are covered. bun test src/ passes, 343 tests across 44 files, and bun run lint:design is clean. I still didn't run bun x tsc --noEmit because the repo has no tsconfig.
1. Terminal door (src/tty.ts)
- Pressing
Son an open listing lists its showings, newest first. Each shows its Fair Housing findings and uses the samelistShowings,addShowingandremoveShowingas the page, so permit and validation are identical. alogs one asdate; buyer's agent; interest; what they said. An empty date means today, and the interest is typed as not interested, maybe, interested or offer likely.dremoves the one under the cursor.- A guest or another user sees nothing and can't start adding one.
- I also found that
addShowingandremoveShowingweren't emittinglisting.changed. They do now, so a write from the terminal, the API, the form or MCP reaches open web pages live. The tty test asserts both events. - The README's terminal paragraph now describes
S.
2. Contract and doorOf tests
src/contract.test.tshas a new test that walks everylisting:updatecell foraddShowing,listShowings,removeShowingand thelog_showingtool. It checks that each answers as the table says, on the user's own listing and on someone else's.src/security.test.tscovers the newdoorOfbranch: POST or DELETE under a row is an update of that row. It also checks that malformed paths, such as a trailing or doubled slash, are not doors.- In
src/showings.test.ts, another user and an anonymous caller get 403, 404 or 401 on the API DELETE, and the showing is still there afterwards. - The form delete is weaker. The test accepts a 303, 403 or 404 for a non-author, then checks the showing survived and the redirect page doesn't say "Showing removed." It pins the outcome, not a status code.
3. Home pill
- A new test renders the home page and asserts
>1 showing</span>and>5 showings</span>for the author. It also asserts that no count appears for another user or for anon. It fails if the pill insrc/pages/home.tsis removed.
I didn't change the contract table.
Earlier asks are met: the terminal door (S: list, a: log, d: remove) uses the same listShowings/addShowing/removeShowing and so the same permit gate, and a write there emits listing.changed. contract.test.ts now has a showings row. The home pill has a rendering test. contract.json is untouched, and the doorOf change only makes mutations stricter. Showings stay off the property page, email and flyer. Note: the clone lacked the old head and the tty.ts hunk was cut from my view, so I judged tty from its tests and the description. Non-blocking: the form-delete refusal test accepts 303/403/404; pinning one status would be tighter.
The listing page now has a Showings section, and the home list shows "N showings" beside each listing that has any.
bun test src/passes: 339 tests across 44 files, including the speed test and the newsrc/showings.test.ts.bun scripts/design-lint.tsdoesn't exist in this repo, so I ranbun run lint:designand it is clean.bun x tsc --noEmitcan't check anything here: there is no tsconfig, so I didn't count it.What changed
showingtable insrc/db.tsis keyed to the listing and cascades on delete, so removing a listing removes its showings.src/showings.ts.- The date defaults to today and can't be in the future. - The buyer's agent is optional, up to 80 characters. - Interest is one of Not interested, Maybe, Interested or Offer likely. - Feedback is one line, up to 280 characters, with no control characters. - A refused field is named in the 422.
permitas an update of the listing.contract.jsonis unchanged.src/pages/showings.ts, newest first. It adds and removes with plain form POSTs and no JavaScript. Each feedback line is Fair Housing checked, with the finding beside it.POST /api/listings/:id/showings,DELETE /api/listings/:id/showings/:showing, ashowingsarray onGET /api/listings/:id, and an MCPlog_showingtool.contract/openapi.jsonis regenerated./api/listings/:idomits them for anyone else.Two things to look at
doorOfinsrc/security.ts. Before, a mutation on/api/listings/:id/showingsmatched no contract cell, so it was not checked as an update. Now a POST or DELETE on a path under a row counts as an update of that row. That is stricter, not looser, and I updated the tool list insrc/server.test.tsforlog_showing.What I didn't do: no edit-in-place for a showing (remove and re-add), and no showings in the terminal.
Closes #81