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
Check · Review · Land · Live done — Landed and live in production.
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
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.tsrenders 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
permitwith the existinglistingread 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 (
originis now passed tolistingPage,emailPageandflyerPage). - JavaScript: the Copy button is a small addition to
static/listing.js. It is hidden unlessnavigator.clipboardexists. The property page itself ships no JavaScript. - Styles and docs:
static/app.cssgets the new styles,docs/design/areas/property.mdnames the new classes, and the README walkthrough shows the page. - Tests:
src/property.test.tscovers 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 tosrc/hostile.test.tsandsrc/speed.test.ts.
Not run or not done
deps.test.tsfailure: "no lockfile or node_modules" fails here only because the sandbox has anode_modulesdirectory. I did not change the test.bun x tsc --noEmit: it cannot run because the repo has notsconfig.json.- Design lint: the repo's script is
scripts/lint-design.ts, not thedesign-lint.tsthe instructions name. It printed nothing. - Gate and fast scripts: the repo has no
scripts/gate.tsorscripts/fast.ts. - Not done: I did not add any other fields or a public listing index.
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.
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 fromFORGE_APP_URL(src/env.ts). Nothing reads the request's Host header any more. I did not keep the request origin as a fallback, becausepublicOriginalready defaults tohttp://localhost, which covers tests. A new test sendsHost: evil.exampleand checks it appears in none of the three pages. I changed the old absolute-URL test to expectpublicOrigininstead of the test server's address. - Terminal and MCP: a new
propertyLineinsrc/listing.tsprintsProperty 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 MCPlaunch_kittool and the terminal'skview all have it. A newtty.test.tscase checks the line before approval and again after pressinga. - Contract row: the "approved words only" rule now lives in a new
getHomeaction insrc/actions.ts, and the route calls it.publicDescriptionmoved tosrc/listing.tsso the action can use it. The newcontract.test.tsrow checks every identity on the listing's own row and on someone else's. Before approval,getHomeanswers like theupdatecell; after approval, like thereadcell. 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/1shows 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/1still returns 404 on a preview with older data, approve the MLS description by hand. The README mentions this and theFORGE_APP_URLrule.
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.
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.
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,listingsandwalkthrough. In a run of everysrc/*.test.ts, one test failed, and that one is environmental (below).What changed
src/pages/property.tsrenders 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.permitwith the existinglistingread cell, so no cell changes.originis now passed tolistingPage,emailPageandflyerPage).static/listing.js. It is hidden unlessnavigator.clipboardexists. The property page itself ships no JavaScript.static/app.cssgets the new styles,docs/design/areas/property.mdnames the new classes, and the README walkthrough shows the page.src/property.test.tscovers 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 tosrc/hostile.test.tsandsrc/speed.test.ts.Not run or not done
deps.test.tsfailure: "no lockfile or node_modules" fails here only because the sandbox has anode_modulesdirectory. I did not change the test.bun x tsc --noEmit: it cannot run because the repo has notsconfig.json.scripts/lint-design.ts, not thedesign-lint.tsthe instructions name. It printed nothing.scripts/gate.tsorscripts/fast.ts.Closes #47