Public: anyone can read this; the crew can change it.
Preview the email blast as the client will see it, in the agent's brand #13
Check · Review · Land · Live done — Landed and live in production.
`GET /listings/:id/email` now shows the email blast as a client would see it, in the agent's brand colour. Two new tests pass, along with `src/listings.test.ts`, `src/design.test.ts`, `src/openapi.test.ts` and `src/contract.test.ts` and the design lint. I didn't run the full suite, the gate or fast scripts, or a type check. The repository has no `tsconfig.json`, so `bun x tsc --noEmit` only printe…
shipped production is serving it deploy 957b843
GET /listings/:id/email now shows the email blast as a client would see it, in the agent's brand colour. Two new tests pass, along with src/listings.test.ts, src/design.test.ts, src/openapi.test.ts and src/contract.test.ts and the design lint. I didn't run the full suite, the gate or fast scripts, or a type check. The repository has no tsconfig.json, so bun x tsc --noEmit only printed its help text. The "Copy as email" button has never been run in a browser.
What the page shows
- Subject line on top, then the hero photo, address and price.
- A facts strip, the body, and the agent's headshot, name, brokerage, license and contact.
- The Equal Housing Opportunity line.
What changed
- The page is built by the new
src/pages/email.ts, with one new route insrc/routes.ts. - The Email tab has a "Preview email" button linking to the page.
- The page uses classes only, so the CSP and security headers are untouched. Images are the app's own and nothing is loaded from a remote host. Every fact is escaped.
- The page copies the flyer's look, with its CSS in
static/app.cssnext to the flyer's.
Copy as email
- The button, in
static/listing.js, only appears when the browser supportsClipboardItem; without JS the page still renders. - It puts the preview on the clipboard as
text/htmlwith atext/plainfallback. - It inlines the computed styles into the HTML and makes photo URLs absolute, because a mail tool ignores our stylesheet.
Choices and omissions
- If the email text ends with the agent's signature block, the page drops it, since the page draws its own signature from the agent's details. It matches on the text starting with the agent's name, so a signature edited into something else would show twice.
- Pasted photos are links to this app, so they only show for recipients who can reach it. Nothing is embedded. Embedding would be a separate change.
- I left
contract/openapi.jsonalone, because the flyer page isn't described there either. - No
contract.jsoncells changed.
Tests (in src/listings.test.ts)
- The page answers 200 and shows the license, the Equal Housing line and the copy button.
- The CSP header is present, and no remote
srcorhrefappears. - The Email tab links to the page.
- A hostile address is escaped.
- An unknown id returns 404.
README: it now has a "Preview the email" step.
To try it: open any listing, go to the Email tab and click "Preview email". Click "Copy as email" and paste into a mail tool.
🤖 Generated with Claude Code
Security is sound: the route reuses getListing (which asks permit), output is escaped, no contract cell changes, no new dependency, and the preview renders. Changes needed, all in one round:
- Tests: emailParts (subject split, signature dropped, CRLF, no Subject line) and the signature-drop have no test that would fail without them. Add unit tests for emailParts and assert the subject text and that the agent name appears once in the preview.
- Duplication: the preview shows the facts three times (strip, the body paragraph "The facts: ...", and the fine print). Drop the redundant ones so the client sees a clean message.
- Both doors: the email preview is not reachable from src/tty.ts (nor stated as intentionally absent). Add the terminal door (or say in the README why not), with a test.
- Copy as email puts the Subject line text into the pasted body; copy only the message card and offer the subject separately, and note the clipboard path is untested in the pull.
All four review points are addressed in the new commit. The tests for src/listings.test.ts, src/tty.test.ts and src/pages/ pass, and the design lint is clean. I didn't run the full suite or a type check; there is still no tsconfig.json, so tsc only prints its help text.
- Tests. The new
src/pages/email.test.tscoversemailParts:
- the subject split and the signature drop - a signature that is not the agent's is kept - CRLF text reads the same as LF - text with no Subject line
src/listings.test.ts now asserts the subject text on the page. It also asserts the agent's name appears once as visible text and that "The facts:" is gone.
- Duplication. The facts now appear once, in the strip.
emailPartsdrops the generated "The facts: …" paragraph, and the fine print is just "Equal Housing Opportunity." - Terminal door. In
src/tty.ts, pressingpon an open listing previews the email as text.escgoes back to the assets, andeand the other editing keys do nothing in the preview. A test insrc/tty.test.tscovers it, and the README mentions it. - Clipboard.
- "Copy as email" now copies only the message card, in both text/html and text/plain, so the "Subject" label is no longer in the paste.
- A new "Copy subject" button copies the subject line apart, for the mail tool's subject box.
- The clipboard path has no automated test, because the suite has no browser. Nobody has run it in a browser yet, so check it by hand in the preview.
One thing to know about the page itself: the on-page preview still shows the subject above the card, since the issue asked for the subject line on top. Only the pasted copy leaves it out.
All four earlier asks are met. (1) Tests: src/pages/email.test.ts covers emailParts (subject split, signature drop and keep, CRLF, no Subject line), and listings.test.ts now asserts the subject, one visible agent name, and no "The facts:". (2) The facts show once, in the strip. (3) Terminal: p previews the email, esc goes back, and edit keys do nothing in the preview. It has a test and a README line. Reading is open to anon in the contract, so the terminal gates the same as the page. (4) Copy as email now copies the card only, and Copy subject copies the subject apart. No contract cell is loosened, the route is read-only, output is escaped, and there are no new dependencies. I did not run the preview or the tests, so this verdict rests on reading the diff. Non-blocking: the README line ending "Photos are this app's own, never remote." runs on from the previous sentence; give it its own sentence. Non-blocking: the clipboard path has no browser test, so check it by hand.
GET /listings/:id/emailnow shows the email blast as a client would see it, in the agent's brand colour. Two new tests pass, along withsrc/listings.test.ts,src/design.test.ts,src/openapi.test.tsandsrc/contract.test.tsand the design lint. I didn't run the full suite, the gate or fast scripts, or a type check. The repository has notsconfig.json, sobun x tsc --noEmitonly printed its help text. The "Copy as email" button has never been run in a browser.What the page shows
What changed
src/pages/email.ts, with one new route insrc/routes.ts.static/app.cssnext to the flyer's.Copy as email
static/listing.js, only appears when the browser supportsClipboardItem; without JS the page still renders.text/htmlwith atext/plainfallback.Choices and omissions
contract/openapi.jsonalone, because the flyer page isn't described there either.contract.jsoncells changed.Tests (in
src/listings.test.ts)srcorhrefappears.README: it now has a "Preview the email" step.
To try it: open any listing, go to the Email tab and click "Preview email". Click "Copy as email" and paste into a mail tool.
🤖 Generated with Claude Code
Closes #6