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

Add an open house to a listing: its date and time on the flyer, the email and every social post #40

Merged opened this

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

Merged

A listing can now carry up to two open houses, and each shows up on the flyer, the email, the MLS description and every social post. `bun test` is green (130 tests) and `bun scripts/lint-design.ts` is clean. I couldn't run `bun x tsc --noEmit`: the repo has no `tsconfig.json` and Bun's type definitions aren't installed here, so `tsc` only printed its help text. I haven't started the app or looked…

shipped production is serving it deploy 957b843

opened this

A listing can now carry up to two open houses, and each shows up on the flyer, the email, the MLS description and every social post. bun test is green (130 tests) and bun scripts/lint-design.ts is clean. I couldn't run bun x tsc --noEmit: the repo has no tsconfig.json and Bun's type definitions aren't installed here, so tsc only printed its help text. I haven't started the app or looked at the new form or the flyer band in a browser.

What changed

  • Storage: migration 11 adds listing.open_houses, a JSON list of {date, start, end}. Dates and times are stored as typed, with no time zone applied. Existing rows get [].
  • Validation (src/listing.ts): a real date (2027-02-30 is refused), HH:MM times, and the end after the start. Blank rows are ignored, more than two is refused, and the list is sorted by date. The form's open1_date, open1_start, open1_end (and open2_…) fields mean the same as open_houses: [...].
  • Words (src/generate.ts): openHouseLine writes it per tone: "Open Sunday, March 7, 1–3 pm.", "Come by the open house: …", "OPEN HOUSE: …". Two open houses read "… and Sunday, …".

- MLS description: it is the last line. - Short post, medium post, carousel: it sits in the post (in the carousel it is its own slide). - Flyer: it ends the copy, and the page lifts it into a band under the price. - Email: it is the line above the body. - Fair Housing: the generated lines pass the check in all three tones.

  • Changing it: updateFacts rewrites every asset and clears every approval, as a price change does. A change to something else keeps the open houses.
  • Doors:

- Pages: the launch form and Edit facts have an "Open house" fieldset; the listing page and the terminal also show it. - JSON API: PATCH /api/listings/:id takes {"facts":{"open_houses":[…]}}. contract/openapi.json is re-emitted. - MCP: launch_listing and update_facts accept open_houses. - Terminal: a new key o on an open listing edits it as 2027-03-09 13:00-15:00; …. An empty line clears it.

  • Sample: 412 Alder Lane is planted with Sunday, March 7, 2027, 1–3 pm. I kept it out of the SAMPLE constant, so the form's placeholders and the tests that post every SAMPLE field are unaffected.
  • Tests: the new src/openhouse.test.ts covers time wording, validation refusals, the line in every asset and tone and none when cleared, approvals cleared on add/change/clear, the form fields, the API, MCP and the terminal. The README walkthrough has a new step 7, "Add an open house" (Print the flyer is now step 8), and src/walkthrough.test.ts walks it.

Decisions to check

  • A past date is allowed. I didn't add a "must be in the future" rule, because the sample would then expire and the issue didn't ask for it.
  • "Start a new one from this" copies the facts but not the open house, since another listing's date shouldn't carry over.
  • No cell in contract.json changed.

To try it: open the sample listing, go to the Flyer tab and Preview email. On your own listing, Edit facts, fill in "Open house", then Save and write all again.

Closes #34

commented

A listing can now carry up to two open houses, and each shows up on the flyer, the email, the MLS description and every social post. bun test is green (130 tests) and bun scripts/lint-design.ts is clean. I couldn't run bun x tsc --noEmit: the repo has no tsconfig.json and Bun's type definitions aren't installed here, so tsc only printed its help text. I haven't started the app or looked at the new form or the flyer band in a browser.

What changed

  • Storage: migration 11 adds listing.open_houses, a JSON list of {date, start, end}. Dates and times are stored as typed, with no time zone applied. Existing rows get [].
  • Validation (src/listing.ts): a real date (2027-02-30 is refused), HH:MM times, and the end after the start. Blank rows are ignored, more than two is refused, and the list is sorted by date. The form's open1_date, open1_start, open1_end (and open2_…) fields mean the same as open_houses: [...].
  • Words (src/generate.ts): openHouseLine writes it per tone: "Open Sunday, March 7, 1–3 pm.", "Come by the open house: …", "OPEN HOUSE: …". Two open houses read "… and Sunday, …".

- MLS description: it is the last line. - Short post, medium post, carousel: it sits in the post (in the carousel it is its own slide). - Flyer: it ends the copy, and the page lifts it into a band under the price. - Email: it is the line above the body. - Fair Housing: the generated lines pass the check in all three tones.

  • Changing it: updateFacts rewrites every asset and clears every approval, as a price change does. A change to something else keeps the open houses.
  • Doors:

- Pages: the launch form and Edit facts have an "Open house" fieldset; the listing page and the terminal also show it. - JSON API: PATCH /api/listings/:id takes {"facts":{"open_houses":[…]}}. contract/openapi.json is re-emitted. - MCP: launch_listing and update_facts accept open_houses. - Terminal: a new key o on an open listing edits it as 2027-03-09 13:00-15:00; …. An empty line clears it.

  • Sample: 412 Alder Lane is planted with Sunday, March 7, 2027, 1–3 pm. I kept it out of the SAMPLE constant, so the form's placeholders and the tests that post every SAMPLE field are unaffected.
  • Tests: the new src/openhouse.test.ts covers time wording, validation refusals, the line in every asset and tone and none when cleared, approvals cleared on add/change/clear, the form fields, the API, MCP and the terminal. The README walkthrough has a new step 7, "Add an open house" (Print the flyer is now step 8), and src/walkthrough.test.ts walks it.

Decisions to check

  • A past date is allowed. I didn't add a "must be in the future" rule, because the sample would then expire and the issue didn't ask for it.
  • "Start a new one from this" copies the facts but not the open house, since another listing's date shouldn't carry over.
  • No cell in contract.json changed.

To try it: open the sample listing, go to the Flyer tab and Preview email. On your own listing, Edit facts, fill in "Open house", then Save and write all again.

Changes requested — before the latest push

Logic, validation, the five doors, tests and the README are sound, and no contract cell is loosened. Two things to fix in one round:

  1. (first) Two new classes have no style anywhere in the head: listing-open (src/pages/listing.ts) and open-grid (src/pages/home.ts). grep finds no rule for either in static/app.css, so the open house line on the listing page is unstyled and the form rows rely on nothing. Either add the rules or reuse an existing class. The one new component class that does have CSS (flyer-open) is not named in a docs/design/areas/<area>.md as the design rules require; name every new class there.
  2. Cleanliness: openHouseLine(...) is computed twice per site (=== null ? ... : openHouseLine(x)!) in listing.ts page, tty.ts and generate.ts (carousel); bind it once. Also openHouses() walks every row of an arbitrarily long open_houses array before the 'at most 2' check; reject rows.length > OPEN_HOUSES_MAX of non-blank rows early, or check the length first.

Also: the builder reports running scripts/lint-design.ts; the instructions name scripts/design-lint.ts, which does not exist in this repo, so confirm the lint that exists is clean on the new CSS/classes. tsc could not be run by the builder; it is not proven here either.

commented

Both review items are fixed and pushed to the same branch. bun test passes (130 tests) and the design lint, run as bun scripts/lint-design.ts, prints nothing. I haven't looked at the styled pages in a browser.

  • Unstyled classes:

- listing-open and open-grid now have rules in static/app.css, built from existing tokens. open-grid uses the same columns as facts-grid. - docs/design/areas/open-house.md is new and names flyer-open, listing-open and open-grid. The repo had no docs/ folder, so I created it.

  • Lint script name: the task text says scripts/design-lint.ts, but the script in this repo is scripts/lint-design.ts, so I ran that one.
  • Double calls: openHouseLine is now computed once per site, in the listing page, the terminal and the carousel. In the listing page and the terminal the variable is whenLine, because open is already a name in those scopes.
  • Row cap: openHouses() counts the non-blank rows first and refuses more than two before checking any of them.
  • tsc: still not run. The repo has no tsconfig.json and the type definitions aren't installed here. The tests exercise every changed path.
Approved

Earlier asks are met. The unstyled classes now have rules in static/app.css, docs/design/areas/open-house.md names them, openHouseLine is computed once per site, and the row cap is checked before the rows are validated. I could not diff against 096df03003cf because the clone lacks that commit, so I read the whole change. Validation is at the boundary and output is escaped. The page, the API, MCP and the terminal all go through updateFacts, so they all ask permit. No contract cell is loosened. Every behaviour has a test, including the terminal key and the API. The README step is there. Non-blocking: tsc was not run because the repo has no tsconfig.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs