Public: anyone can read this; the crew can change it.

Download a listing's open house visitors as a CSV file the agent can import into their CRM #111

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

The listing's author can now download their open house visitors as a CSV at `GET /listings/:id/visitors.csv`. The new test and the related suites I ran pass; I did not run the full suite. I also ran the design lint, which printed nothing. The type check did nothing here (the repo has no `tsconfig.json`, and `bun x tsc --noEmit` only printed tsc's help), and `scripts/gate.ts` and `scripts/fast.ts`…

shipped production is serving it deploy 957b843

opened this

The listing's author can now download their open house visitors as a CSV at GET /listings/:id/visitors.csv. The new test and the related suites I ran pass; I did not run the full suite. I also ran the design lint, which printed nothing. The type check did nothing here (the repo has no tsconfig.json, and bun x tsc --noEmit only printed tsc's help), and scripts/gate.ts and scripts/fast.ts don't exist in this clone, so the gate and fast-test selection did not run.

  • Link: "Download visitors (CSV)" sits under the "Would go to" list on the follow-up email page (/listings/:id/email?which=followup), and only shows when there is at least one visitor. The issue says "the visitors section on the listing"; that list is the visitors section, but it lives on the email page rather than the property page.
  • File: it is text/csv; charset=utf-8, sent as an attachment named like 412-alder-lane-visitors.csv. The header row is Name,Email,Open house,Listing, with one row per visitor.
  • Open house column: visitors aren't tied to a particular open house in the database, so every row carries all of the listing's open houses, e.g. 2027-03-07 13:00-15:00; ….
  • Quoting: every field is quoted per RFC 4180, with lines ending in CRLF. A field starting with =, +, -, @, tab or carriage return gets a leading '.
  • Code: visitorsCsv in src/visitors.ts is a pure function. The route is in src/routes.ts. I split addressSlug out of kitFilename in src/kit.ts so both filenames share it.
  • Access: the route asks listVisitors, which goes through permit on the listing's existing update cell. Anyone else gets the same answer as the other author-only pages, with no email in it. No cell in contract.json changed. The file is sent with cache-control: no-store.
  • Tests: in src/followup.test.ts, a hostile visitor (=HYPERLINK("http://x","a"), @evil, Jo, -1@…, and an address with quotes) proves the quoting and the formula prefix. An end-to-end test shows the author gets the file, a stranger and anon get no emails, and the property page and launch kit never contain the visitor's email.
  • README: the open house section names the download.

I left the terminal, MCP and API docs alone, as the issue allows.

To try it, launch a listing with an open house, add a visitor on the follow-up email page, and click the link.

🤖 Generated with Claude Code

Closes #93

commented

The listing's author can now download their open house visitors as a CSV at GET /listings/:id/visitors.csv. The new test and the related suites I ran pass; I did not run the full suite. I also ran the design lint, which printed nothing. The type check did nothing here (the repo has no tsconfig.json, and bun x tsc --noEmit only printed tsc's help), and scripts/gate.ts and scripts/fast.ts don't exist in this clone, so the gate and fast-test selection did not run.

  • Link: "Download visitors (CSV)" sits under the "Would go to" list on the follow-up email page (/listings/:id/email?which=followup), and only shows when there is at least one visitor. The issue says "the visitors section on the listing"; that list is the visitors section, but it lives on the email page rather than the property page.
  • File: it is text/csv; charset=utf-8, sent as an attachment named like 412-alder-lane-visitors.csv. The header row is Name,Email,Open house,Listing, with one row per visitor.
  • Open house column: visitors aren't tied to a particular open house in the database, so every row carries all of the listing's open houses, e.g. 2027-03-07 13:00-15:00; ….
  • Quoting: every field is quoted per RFC 4180, with lines ending in CRLF. A field starting with =, +, -, @, tab or carriage return gets a leading '.
  • Code: visitorsCsv in src/visitors.ts is a pure function. The route is in src/routes.ts. I split addressSlug out of kitFilename in src/kit.ts so both filenames share it.
  • Access: the route asks listVisitors, which goes through permit on the listing's existing update cell. Anyone else gets the same answer as the other author-only pages, with no email in it. No cell in contract.json changed. The file is sent with cache-control: no-store.
  • Tests: in src/followup.test.ts, a hostile visitor (=HYPERLINK("http://x","a"), @evil, Jo, -1@…, and an address with quotes) proves the quoting and the formula prefix. An end-to-end test shows the author gets the file, a stranger and anon get no emails, and the property page and launch kit never contain the visitor's email.
  • README: the open house section names the download.

I left the terminal, MCP and API docs alone, as the issue allows.

To try it, launch a listing with an open house, add a visitor on the follow-up email page, and click the link.

🤖 Generated with Claude Code

Approved

Clean. The route reads the listing, then lists visitors through listVisitors, which asks permit on the existing update cell. Strangers and anon get no data (the preview answers 401 to an unsigned request). No contract cell changes. Every CSV field is quoted with quotes doubled, formula leaders are defused, and the hostile-visitor tests cover both. The filename comes from a shared addressSlug, the response is no-store with the security headers, and the README names the download.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs