Public: anyone can read this; the crew can change it.
The README meeting walkthrough covers the week view, showings, the seller update and link previews, and its test walks them #99
Check · Review · Land · Live done — Landed and live in production.
mason/listing-launch:mason/97-the-readme-meeting-walkthrough-covers-th → main 1 commit fork
2 files
changed +89
−0
README.md +23
| @@ -153,6 +153,29 @@ to make a listing of your own. | ||
| 153 | 153 | In the terminal it is the last asset of the listing: `j` to it, `a` to approve |
| 154 | 154 | or withdraw. The seller report leaves it out; it is a note, not something |
| 155 | 155 | that goes out. |
| 156 | 16. **See the coming week.** Press **This week** above the list on the first | |
| 157 | screen: the next seven days, a section for each day, with every listing's | |
| 158 | posts, emails and open houses that go out that day, and who approved each | |
| 159 | (or Draft). At the top, **Still needs approval** names each listing with | |
| 160 | some of its words not yet approved, such as "3 of 7 approved". *412 Alder | |
| 161 | Lane* shows its open house there. **Add to calendar** puts the same week | |
| 162 | in your calendar. Nothing is sent from this page. | |
| 163 | 17. **Write down a showing.** On your own listing, scroll to **Showings**. | |
| 164 | Leave the day as today, type the buyer's agent if you know them, pick how | |
| 165 | interested they were (Not interested, Maybe, Interested or Offer likely), | |
| 166 | write one line of what they said and press **Log showing**. It lands at the | |
| 167 | top of the list, newest first, and the first screen now shows "1 showing" | |
| 168 | beside your listing. **Remove** takes it back out. Only you see this log: | |
| 169 | it is not on the property page, the flyer or the email. (The sample | |
| 170 | listings belong to the sample agent, so their showings are hers to see.) | |
| 171 | 18. **Approve the seller update.** On your listing open the **Seller update** | |
| 172 | tab: the weekly email to your seller, already written, with what you | |
| 173 | approved this week in it. Read it, then press **Approve**. Until you do, it says "Draft — not approved". | |
| 174 | 19. **Paste the property page link.** On a listing, copy the property page | |
| 175 | link (the **Copy** button) and paste it into a text or a post: the phone | |
| 176 | shows a preview with the address, the price, the beds and baths and the | |
| 177 | first photo. *412 Alder Lane* is approved, so its link works for anyone; | |
| 178 | yours shows a page only once you approve the MLS description. | |
| 156 | 179 | |
| 157 | 180 | `src/walkthrough.test.ts` walks these steps on every `bun test`, so this |
| 158 | 181 | list cannot drift from the app. |
src/walkthrough.test.ts +66
| @@ -266,3 +266,69 @@ test("every step of the section works, with the labels it names", async () => { | ||
| 266 | 266 | expect(await (await get(mine)).text()).toContain("data-copy-link="); |
| 267 | 267 | expect((await fetch(`${base}${mine.replace("/listings/", "/homes/")}`)).status).toBe(404); |
| 268 | 268 | }); |
| 269 | ||
| 270 | test("the week, a showing, the seller update and the link preview, as the section says", async () => { | |
| 271 | const home = await (await get("/")).text(); | |
| 272 | const sample = idOf(home, "412 Alder Lane"); | |
| 273 | const mine = `/listings/${idOf(home, "9 Walkthrough Way")}`; | |
| 274 | ||
| 275 | // 16. The coming week. | |
| 276 | for (const label of ["This week", "Still needs approval", "Add to calendar"]) expect(section).toContain(label); | |
| 277 | expect(home).toMatch(control("This week")); | |
| 278 | const week = await get("/week"); | |
| 279 | expect(week.status).toBe(200); | |
| 280 | const weekHtml = await week.text(); | |
| 281 | expect(weekHtml).toContain("Still needs approval"); | |
| 282 | expect(weekHtml).toContain("412 Alder Lane"); | |
| 283 | expect(weekHtml).toContain("Draft — not approved yet"); | |
| 284 | expect(weekHtml).toMatch(control("Add to calendar")); | |
| 285 | ||
| 286 | // 17. Write down a showing. | |
| 287 | for (const label of ["Showings", "Log showing", "Remove"]) expect(section).toContain(label); | |
| 288 | for (const word of ["Not interested", "Maybe", "Interested", "Offer likely"]) expect(section).toContain(word); | |
| 289 | const before = await (await get(mine)).text(); | |
| 290 | expect(before).toContain('id="showings"'); | |
| 291 | expect(before).toMatch(control("Log showing")); | |
| 292 | expect(before).not.toContain("1 showing"); | |
| 293 | const showing = new FormData(); | |
| 294 | showing.set("agent", "Pat Buyer, Walk Realty"); | |
| 295 | showing.set("interest", "interested"); | |
| 296 | showing.set("feedback", "Loved the kitchen."); | |
| 297 | expect((await post(`${mine}/showings`, showing)).status).toBe(303); | |
| 298 | const logged = await (await get(mine)).text(); | |
| 299 | expect(logged).toContain("Loved the kitchen."); | |
| 300 | expect(logged).toContain("Pat Buyer, Walk Realty"); | |
| 301 | expect(logged).toMatch(control("Remove")); | |
| 302 | expect(await (await get("/")).text()).toContain("1 showing"); | |
| 303 | // The sample's showings belong to the sample agent: this realtor sees none, and so the section says. | |
| 304 | expect(await (await get(`/listings/${sample}`)).text()).not.toContain('id="showings"'); | |
| 305 | // Only the realtor: nothing of it reaches the property page's public view. | |
| 306 | expect(await (await fetch(base + mine.replace("/listings/", "/homes/"))).text()).not.toContain("Pat Buyer"); | |
| 307 | ||
| 308 | // 18. The seller update waits for approval, then is approved. | |
| 309 | expect(section).toContain("**Seller update**"); | |
| 310 | expect(logged).toContain("Seller update"); | |
| 311 | expect(logged).toContain("Draft — not approved"); | |
| 312 | const block = /<section class="asset" id="email_update"[^]*?<\/section>/.exec(logged)![0]; | |
| 313 | expect(block).toMatch(/<button[^>]*formaction="[^"]*\/assets\/email_update\/approve"[^>]*>Approve</); | |
| 314 | const update = new FormData(); | |
| 315 | update.set("approved", "1"); | |
| 316 | update.set("seen", /name="seen" value="([^"]+)"/.exec(block)![1]!.replaceAll("&", "&")); | |
| 317 | update.set("text", textareaOf(block)); | |
| 318 | expect((await post(`${mine}/assets/email_update/approve`, update)).status).toBe(303); | |
| 319 | const after = await (await get(mine)).text(); | |
| 320 | const approvedBlock = /<section class="asset" id="email_update"[^]*?<\/section>/.exec(after)![0]; | |
| 321 | expect(approvedBlock).toContain("✓ Approved by"); | |
| 322 | expect(approvedBlock).toContain("Withdraw"); | |
| 323 | ||
| 324 | // 19. The link preview: an approved listing's page carries the tags a phone reads. | |
| 325 | expect(section).toContain("Copy"); | |
| 326 | expect(section).toContain("412 Alder Lane"); | |
| 327 | const page = await fetch(`${base}/homes/${sample}`); | |
| 328 | expect(page.status).toBe(200); | |
| 329 | const preview = await page.text(); | |
| 330 | expect(preview).toMatch(/<meta property="og:title" content="412 Alder Lane[^"]*\$\d/); | |
| 331 | expect(preview).toMatch(/<meta property="og:image" content="https?:\/\/[^"]+"/); | |
| 332 | expect(preview).toContain('name="twitter:card"'); | |
| 333 | expect(await (await get(mine)).text()).toContain("data-copy-link="); | |
| 334 | }); |