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

Make a square picture for each listing's social posts: hero photo, price and a Just listed / Open house / Sold band in the agent's brand #66

Merged opened this

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

Merged

`GET /listings/:id/card` now serves a 1080×1080 square picture, and a **Square picture** link on the Social tab opens it. The new `card`, `hostile` and `speed` tests pass, and `bun scripts/lint-design.ts` is clean. `bun x tsc --noEmit` didn't run: the repo has no `tsconfig.json`, so `tsc` only printed its help. I didn't run the whole suite; the forge's check does.

shipped production is serving it deploy 957b843

opened this

GET /listings/:id/card now serves a 1080×1080 square picture, and a Square picture link on the Social tab opens it. The new card, hostile and speed tests pass, and bun scripts/lint-design.ts is clean. bun x tsc --noEmit didn't run: the repo has no tsconfig.json, so tsc only printed its help. I didn't run the whole suite; the forge's check does.

What changed

  • Page: src/pages/card.ts is new, with cardPage and cardHeadline. It uses the flyer's print layout and print bar.
  • Layout: the hero photo fills the square, with a band in the listing's brand colour along the bottom. The band holds the headline, price, beds · baths · sq ft, address, and the agent's name, brokerage and phone. Everything on it comes from listing facts and the agent's brand, never asset text.
  • Headline order: Sold, then Under contract, then New price (a lowered price), then Open house Sun, Mar 7, 1–3 pm, then Just listed. I added "Coming soon" for coming-soon listings, which the issue didn't list. Without it they would have read "Just listed".
  • Open house wording: it is a short form of the flyer's date and time, via a new whenShort in src/generate.ts, so it fits the band. The flyer says "Open Sunday, March 7", so the words are not identical. The card uses the listing's first open house.
  • Draft band: draftBand now also accepts "social_short". I read "the first social post" as the short post, so approving that one removes the band.
  • No photo: the band's brand colour fills the square in the same layout, with no <img>.
  • Access and routing: the route is in src/routes.ts and reads through getListing, so it follows the existing listing read cell, with no contract change. An unknown id is 404.
  • Styles and docs: the CSS is in static/app.css, with the new classes named in docs/design/areas/card.md. The README walkthrough gains a paragraph saying plainly that saving as PDF or taking a screenshot is how you get the picture.
  • Fit: long addresses wrap and clamp to two lines, and the band has a maximum height, so a long address or seven-figure price can't overflow the square.
  • Print: an @page card rule makes the printed page 1080 px square. I did not check in a browser that it prints as one page.
  • Tests:

- New src/card.test.ts: each headline in order, the draft band before and after approving the first post, no photo, the link, and 404. - src/hostile.test.ts and src/speed.test.ts now include /listings/:id/card. The hostile test uses the hostile listing, but I did not add a dedicated test for the longest address.

I added no JavaScript, no dependencies, and no changes to any permission cell.

Try it: open a listing, go to the Social tab and press Square picture, then Print and save as PDF.

Closes #53

commented

GET /listings/:id/card now serves a 1080×1080 square picture, and a Square picture link on the Social tab opens it. The new card, hostile and speed tests pass, and bun scripts/lint-design.ts is clean. bun x tsc --noEmit didn't run: the repo has no tsconfig.json, so tsc only printed its help. I didn't run the whole suite; the forge's check does.

What changed

  • Page: src/pages/card.ts is new, with cardPage and cardHeadline. It uses the flyer's print layout and print bar.
  • Layout: the hero photo fills the square, with a band in the listing's brand colour along the bottom. The band holds the headline, price, beds · baths · sq ft, address, and the agent's name, brokerage and phone. Everything on it comes from listing facts and the agent's brand, never asset text.
  • Headline order: Sold, then Under contract, then New price (a lowered price), then Open house Sun, Mar 7, 1–3 pm, then Just listed. I added "Coming soon" for coming-soon listings, which the issue didn't list. Without it they would have read "Just listed".
  • Open house wording: it is a short form of the flyer's date and time, via a new whenShort in src/generate.ts, so it fits the band. The flyer says "Open Sunday, March 7", so the words are not identical. The card uses the listing's first open house.
  • Draft band: draftBand now also accepts "social_short". I read "the first social post" as the short post, so approving that one removes the band.
  • No photo: the band's brand colour fills the square in the same layout, with no <img>.
  • Access and routing: the route is in src/routes.ts and reads through getListing, so it follows the existing listing read cell, with no contract change. An unknown id is 404.
  • Styles and docs: the CSS is in static/app.css, with the new classes named in docs/design/areas/card.md. The README walkthrough gains a paragraph saying plainly that saving as PDF or taking a screenshot is how you get the picture.
  • Fit: long addresses wrap and clamp to two lines, and the band has a maximum height, so a long address or seven-figure price can't overflow the square.
  • Print: an @page card rule makes the printed page 1080 px square. I did not check in a browser that it prints as one page.
  • Tests:

- New src/card.test.ts: each headline in order, the draft band before and after approving the first post, no photo, the link, and 404. - src/hostile.test.ts and src/speed.test.ts now include /listings/:id/card. The hostile test uses the hostile listing, but I did not add a dedicated test for the longest address.

I added no JavaScript, no dependencies, and no changes to any permission cell.

Try it: open a listing, go to the Social tab and press Square picture, then Print and save as PDF.

Changes requested — before the latest push

The card works in the preview, the route goes through getListing (no contract change), no JS, and design-lint is clean. Two things to fix in one round:

  1. README walkthrough is malformed: the new paragraph is an unnumbered, oddly indented block after item 10, so it renders as part of the flyer step (or breaks the list). Make it its own numbered step and renumber the following steps (and any later references to step numbers), and keep it short, per the issue: one line.
  2. The "Coming soon" headline is a behaviour the issue did not ask for and it has no test in src/card.test.ts. Either drop it or add a test that would fail without it (and have the card ignore open houses for a coming-soon listing the way the property page does, which only shows open houses when status is active). Also your pull text names scripts/lint-design.ts; the check named in the issue is scripts/design-lint.ts - make sure that is the one that passes.

Everything else (headline order, draft band on social_short, no-photo fallback, 404, hostile and speed entries) is fine.

commented

Both points are fixed and pushed. The card and walkthrough tests pass.

  • README: the square picture is now its own step 11, one line. The steps after it are renumbered 12–14, and nothing else in the README refers to step numbers.
  • Coming soon: I dropped that headline. A coming-soon listing now reads "Just listed". The card also shows an open house only when the listing is active, as the property page does. A new test in src/card.test.ts checks that a coming-soon listing with an open house still reads "Just listed".
  • Design lint: the repository has no scripts/design-lint.ts, so bun scripts/design-lint.ts fails with "Module not found". The script that exists is scripts/lint-design.ts, and it passes with no output. I ran that one.
Approved

Both earlier asks are met: the README has the square picture as its own step 11 with the later steps renumbered, and the Coming soon headline is gone, so a coming-soon listing reads Just listed. The route goes through getListing, which uses the existing listing read cell, so no cell changes. The page escapes every field and has no new JavaScript. It is added to the hostile and speed tests, and card.test.ts covers each headline, the draft band and the 404. Non-blocking: the card shows only the first open house, so a listing with several shows one.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs