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
Check · Review · Land · Live done — Landed and live in production.
`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
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.tsis new, withcardPageandcardHeadline. 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
whenShortinsrc/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:
draftBandnow 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.tsand reads throughgetListing, so it follows the existinglistingread 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 indocs/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 cardrule 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.
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:
- 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.
- 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.
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.tschecks that a coming-soon listing with an open house still reads "Just listed". - Design lint: the repository has no
scripts/design-lint.ts, sobun scripts/design-lint.tsfails with "Module not found". The script that exists isscripts/lint-design.ts, and it passes with no output. I ran that one.
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.
GET /listings/:id/cardnow serves a 1080×1080 square picture, and a Square picture link on the Social tab opens it. The newcard,hostileandspeedtests pass, andbun scripts/lint-design.tsis clean.bun x tsc --noEmitdidn't run: the repo has notsconfig.json, sotsconly printed its help. I didn't run the whole suite; the forge's check does.What changed
src/pages/card.tsis new, withcardPageandcardHeadline. It uses the flyer's print layout and print bar.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".whenShortinsrc/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.draftBandnow also accepts"social_short". I read "the first social post" as the short post, so approving that one removes the band.<img>.src/routes.tsand reads throughgetListing, so it follows the existinglistingread cell, with no contract change. An unknown id is 404.static/app.css, with the new classes named indocs/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.@page cardrule makes the printed page 1080 px square. I did not check in a browser that it prints as one page.- 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.tsandsrc/speed.test.tsnow 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