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

Give each listing a property page to link from the posts and the email, showing only approved words #50

Merged opened this

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

Merged

Each listing now has a buyer-facing property page at `GET /homes/:id`, linked from the listing page, the email blast and the flyer. The tests I ran pass: the five files that cover this change, `src/property.test.ts`, `hostile`, `speed`, `listings` and `walkthrough`. In a run of every `src/*.test.ts`, one test failed, and that one is environmental (below).

shipped production is serving it deploy 957b843

opened this

Each listing now has a buyer-facing property page at GET /homes/:id, linked from the listing page, the email blast and the flyer. The tests I ran pass: the five files that cover this change, src/property.test.ts, hostile, speed, listings and walkthrough. In a run of every src/*.test.ts, one test failed, and that one is environmental (below).

What changed

  • The page: src/pages/property.ts renders the photos (hero first), address, price, beds, baths, square feet, lot, year built, status (Active, Under contract or Sold) and any open house. It also shows the agent's name, phone, email and brand colour. It is server-rendered with no edit control, no approval pill and no draft text.
  • The MLS description: it shows only when approved. Until then, anon and any other non-author get a 404. The author sees a "Not public yet: approve the MLS description" notice. Reading goes through permit with the existing listing read cell, so no cell changes.
  • Sold listings: a Sold listing keeps its page and shows Sold.
  • Links: the listing page shows the property page link with a Copy button. The email button and the flyer use the absolute URL from the request's origin (origin is now passed to listingPage, emailPage and flyerPage).
  • JavaScript: the Copy button is a small addition to static/listing.js. It is hidden unless navigator.clipboard exists. The property page itself ships no JavaScript.
  • Styles and docs: static/app.css gets the new styles, docs/design/areas/property.md names the new classes, and the README walkthrough shows the page.
  • Tests: src/property.test.ts covers the approved description showing, a draft never appearing and anon getting 404 before approval. It also covers the author's notice, withdrawing approval hiding the page again, a sold listing keeping its page, the absolute links, and 404 for an unknown home. The email-links-to-it case is covered there too. The page is added to src/hostile.test.ts and src/speed.test.ts.

Not run or not done

  • deps.test.ts failure: "no lockfile or node_modules" fails here only because the sandbox has a node_modules directory. I did not change the test.
  • bun x tsc --noEmit: it cannot run because the repo has no tsconfig.json.
  • Design lint: the repo's script is scripts/lint-design.ts, not the design-lint.ts the instructions name. It printed nothing.
  • Gate and fast scripts: the repo has no scripts/gate.ts or scripts/fast.ts.
  • Not done: I did not add any other fields or a public listing index.

Closes #47

commented

Each listing now has a buyer-facing property page at GET /homes/:id, linked from the listing page, the email blast and the flyer. The tests I ran pass: the five files that cover this change, src/property.test.ts, hostile, speed, listings and walkthrough. In a run of every src/*.test.ts, one test failed, and that one is environmental (below).

What changed

  • The page: src/pages/property.ts renders the photos (hero first), address, price, beds, baths, square feet, lot, year built, status (Active, Under contract or Sold) and any open house. It also shows the agent's name, phone, email and brand colour. It is server-rendered with no edit control, no approval pill and no draft text.
  • The MLS description: it shows only when approved. Until then, anon and any other non-author get a 404. The author sees a "Not public yet: approve the MLS description" notice. Reading goes through permit with the existing listing read cell, so no cell changes.
  • Sold listings: a Sold listing keeps its page and shows Sold.
  • Links: the listing page shows the property page link with a Copy button. The email button and the flyer use the absolute URL from the request's origin (origin is now passed to listingPage, emailPage and flyerPage).
  • JavaScript: the Copy button is a small addition to static/listing.js. It is hidden unless navigator.clipboard exists. The property page itself ships no JavaScript.
  • Styles and docs: static/app.css gets the new styles, docs/design/areas/property.md names the new classes, and the README walkthrough shows the page.
  • Tests: src/property.test.ts covers the approved description showing, a draft never appearing and anon getting 404 before approval. It also covers the author's notice, withdrawing approval hiding the page again, a sold listing keeping its page, the absolute links, and 404 for an unknown home. The email-links-to-it case is covered there too. The page is added to src/hostile.test.ts and src/speed.test.ts.

Not run or not done

  • deps.test.ts failure: "no lockfile or node_modules" fails here only because the sandbox has a node_modules directory. I did not change the test.
  • bun x tsc --noEmit: it cannot run because the repo has no tsconfig.json.
  • Design lint: the repo's script is scripts/lint-design.ts, not the design-lint.ts the instructions name. It printed nothing.
  • Gate and fast scripts: the repo has no scripts/gate.ts or scripts/fast.ts.
  • Not done: I did not add any other fields or a public listing index.
Changes requested — before the latest push

Two changes. (1) Both doors: the terminal (src/tty.ts) never shows or copies the property page link, and the MCP/launch kit don't carry it, so an agent or a terminal user cannot reach the new page. Show the absolute property URL (and the 'Not public yet' state) in the tty listing view and add a tty test. (2) The absolute URL is built from c.url.origin, i.e. whatever Host the request carried; behind the forge's proxy that may be an internal origin, and a forged Host puts an attacker's domain in the flyer/email the agent sends to buyers. Take the origin from a configured public base (env such as FORGE_APP_URL) with the request origin only as a fallback in tests, and test that a spoofed Host does not leak into the email link. Also: the new /homes/:id route has no row in src/contract.test.ts; add one (anon reads approved, anon 404 unapproved, author reads). The preview /homes/1 returns 404 for the sample listing since it is unapproved, so the page cannot be seen working there; approve the sample MLS text or show it in the preview.

commented

I addressed all of sentinel's points and pushed to the same branch. bun test src passes except the sandbox-only deps.test.ts (its check that no node_modules exists fails here because the sandbox has one). bun x tsc --noEmit still cannot run because the repo has no tsconfig.json.

  • Public origin: the listing page, flyer and email now build the property URL from publicOrigin, which comes from FORGE_APP_URL (src/env.ts). Nothing reads the request's Host header any more. I did not keep the request origin as a fallback, because publicOrigin already defaults to http://localhost, which covers tests. A new test sends Host: evil.example and checks it appears in none of the three pages. I changed the old absolute-URL test to expect publicOrigin instead of the test server's address.
  • Terminal and MCP: a new propertyLine in src/listing.ts prints Property page: <url>. When the MLS description is unapproved it adds (not public yet: approve the MLS description). The terminal's listing view shows it. kit() carries it too, so the launch kit download, the MCP launch_kit tool and the terminal's k view all have it. A new tty.test.ts case checks the line before approval and again after pressing a.
  • Contract row: the "approved words only" rule now lives in a new getHome action in src/actions.ts, and the route calls it. publicDescription moved to src/listing.ts so the action can use it. The new contract.test.ts row checks every identity on the listing's own row and on someone else's. Before approval, getHome answers like the update cell; after approval, like the read cell. So anon gets a 404 before approval, anon reads once approved, and the author reads either way. No cell changed.
  • Preview: the seed now approves the Alder Lane sample's MLS text, so /homes/1 shows a page in the preview. Samples are planted once, so this applies to fresh databases only; the existing preview and production data will not pick it up. If /homes/1 still returns 404 on a preview with older data, approve the MLS description by hand. The README mentions this and the FORGE_APP_URL rule.
commented

The preview deploy failed because of a README wording change I made in the last push. I've fixed it and pushed. I haven't seen the forge's re-run of the check.

The walkthrough test (src/walkthrough.test.ts) bans jargon words in the README's "Try it in five minutes" section, and my added sentence used "Host header". It now says the link's origin is FORGE_APP_URL, never whatever address the visitor typed. No code changed.

I re-ran bun test src here. All tests pass except deps.test.ts's "no lockfile or node_modules" check, which fails only because the sandbox has a node_modules directory.

Approved

Earlier asks are met. (1) The contract row is there: getHome asks the listing read cell once approved and the update cell before, with a per-identity test, and no cell in contract.json changed. (2) Links that leave the app now use the configured publicOrigin, and a spoofed-Host test covers the listing page, flyer and email. The terminal and the launch kit also print the property URL and whether it is public yet. Output is escaped, the draft text never reaches the page, and the page is added to the hostile and speed tests. Non-blocking: /homes/1 returns 404 on this preview because the seed approval only applies to fresh databases, and the README says so.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs