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

Open the meeting on a finished launch: the 412 Alder Lane sample shows all six assets approved by its sample agent #54

Merged opened this

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

Merged

mason/listing-launch:mason/52-open-the-meeting-on-a-finished-launch-th → main 1 commit fork

  • Merged. mason/52-open-the-meeting-on-a-finished-launch-th was merged into main as 3393bbf.

Review

Sign in to review this change.
6 files changed +80 −8
README.md +4 −1
@@ -9,7 +9,10 @@ to make a listing of your own.
99
1010 1. **Open the sample.** On the first screen, under "Launched", press the
1111 card for *412 Alder Lane, Portland*. Its page shows the price, the
12 photos and the words, in tabs: MLS, Social, Flyer and Email.
12 photos and the words, in tabs: MLS, Social, Flyer and Email. It opens
13 finished: all six assets approved (the pill reads "Ready to send"), each signed off by "Morgan Reyes
14 (sample)", so you see what a launch looks like at the end. (Birchwood
15 Court stays at 0, and your own copy starts at 0.)
1316 2. **Read an asset and its check.** The MLS tab has the description in a box,
1417 and beside it the **Fair Housing** check: green when it is clear. Open the
1518 card for *27 Birchwood Court, Boise* to see it catch something: the
src/db.ts +7
@@ -97,6 +97,13 @@ export const migrations: string[] = [
9797 `ALTER TABLE listing ADD COLUMN sold_price INTEGER`,
9898 // The price before it was lowered, while it stands below it; the price improvement assets are written from it.
9999 `ALTER TABLE listing ADD COLUMN was_price INTEGER`,
100 // Alder Lane, the planted sample (author 'sample', its own headshot), opens finished: each untouched
101 // asset is signed off by its sample agent, through 'sample', never a person's door. An edited,
102 // already-approved or removed sample is left alone; the one earlier approval planted is re-signed.
103 `UPDATE asset SET approved_by_id = 'sample', approved_by_name = 'Morgan Reyes (sample)',
104 approved_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), approved_via = 'sample'
105 WHERE edited = 0 AND (approved_at IS NULL OR approved_by_id = 'sample')
106 AND listing_id IN (SELECT id FROM listing WHERE author_id = 'sample' AND headshot = 'sample-agent.svg')`,
100107 ];
101108
102109 export function openDb(dir = dataDir): Database {
src/pages/listing.ts +2 −2
@@ -118,9 +118,9 @@ export const draftBand = (l: Launched, kind: "flyer" | "email"): string =>
118118
119119 const DOORS: Record<string, string> = { page: "the page", api: "the API", tty: "the terminal" };
120120
121 /** Where an approval came from, beside who gave it; nothing for one given before it was recorded. */
121 /** Where an approval came from, beside who gave it; nothing for one given before it was recorded, or by a sample (its name already says so). */
122122 const via = (door: string | null): string =>
123 door !== null && door in DOORS ? ` · via ${DOORS[door]}` : "";
123 door !== null && door !== "sample" && door in DOORS ? ` · via ${DOORS[door]}` : "";
124124
125125 /** The approve control beside Copy: a button naming the flags, or who signed off and a way to take it back. */
126126 export function approval(l: Launched, a: Asset): string {
src/sample.test.ts +59 −2
@@ -4,9 +4,12 @@ import { expect, test } from "bun:test";
44 import { mkdtempSync } from "node:fs";
55 import { tmpdir } from "node:os";
66 import { join } from "node:path";
7 import { openDb } from "./db.ts";
7 import { getListing, launchListing } from "./actions.ts";
8 import { migrate, openDb } from "./db.ts";
9 import { kit } from "./kit.ts";
10 import { draftBand } from "./pages/listing.ts";
811 import { check } from "./fairhousing.ts";
9 import { seed } from "./sample.ts";
12 import { SAMPLE, seed } from "./sample.ts";
1013
1114 const fresh = () => openDb(mkdtempSync(join(tmpdir(), "sample-")));
1215 const count = (db: ReturnType<typeof fresh>) =>
@@ -45,3 +48,57 @@ test("the house opens amber, the condo and Alder Lane green", () => {
4548 expect(flags("88 Meridian%")).toBe(0);
4649 expect(flags("412 Alder%")).toBe(0);
4750 });
51
52 const ANON = { role: "anon" } as const;
53 const idOf = (db: ReturnType<typeof fresh>, like: string) =>
54 db.query<{ id: number }, [string]>("SELECT id FROM listing WHERE address LIKE ?1").get(like)!.id;
55 const approvedOf = (db: ReturnType<typeof fresh>, like: string) => {
56 const r = getListing(db, ANON, { id: idOf(db, like) });
57 if (!r.ok) throw new Error("no listing");
58 return r.value.assets.filter((a) => a.approval !== null);
59 };
60
61 test("Alder Lane opens with all six assets approved by its sample agent; Birchwood has none", () => {
62 const db = fresh();
63 seed(db);
64 const alder = approvedOf(db, "412 Alder%");
65 expect(alder).toHaveLength(6);
66 for (const a of alder) expect(a.approval).toMatchObject({ by_name: "Morgan Reyes (sample)", via: "sample" });
67 expect(approvedOf(db, "27 Birchwood%")).toHaveLength(0);
68 expect(approvedOf(db, "88 Meridian%")).toHaveLength(0);
69 const listing = getListing(db, ANON, { id: idOf(db, "412 Alder%") });
70 expect(listing.ok && kit(listing.value)).toContain("Approved by Morgan Reyes (sample) on ");
71 expect(listing.ok && draftBand(listing.value, "flyer")).toBe("");
72 expect(listing.ok && draftBand(listing.value, "email")).toBe("");
73 });
74
75 test("the migration approves an old sample once, and leaves edited or approved rows alone", () => {
76 const db = fresh();
77 seed(db);
78 const id = idOf(db, "412 Alder%");
79 db.run("UPDATE asset SET approved_by_id = NULL, approved_by_name = NULL, approved_at = NULL, approved_via = NULL");
80 db.run(`UPDATE asset SET edited = 1 WHERE kind = 'flyer' AND listing_id = ${id}`);
81 db.run(`UPDATE asset SET approved_by_id = 'u1', approved_by_name = 'Pat', approved_at = '2026-01-01T00:00:00Z', approved_via = 'page' WHERE kind = 'email' AND listing_id = ${id}`);
82 db.run("DELETE FROM schema_version WHERE version = (SELECT max(version) FROM schema_version)");
83 migrate(db);
84 const rows = () =>
85 db.query<{ kind: string; approved_by_name: string | null }, [number]>("SELECT kind, approved_by_name FROM asset WHERE listing_id = ?1").all(id);
86 const names = Object.fromEntries(rows().map((r) => [r.kind, r.approved_by_name]));
87 expect(names.mls).toBe("Morgan Reyes (sample)");
88 expect(names.flyer).toBeNull();
89 expect(names.email).toBe("Pat");
90 expect(approvedOf(db, "27 Birchwood%")).toHaveLength(0);
91 // Second time: nothing to run, so a later withdrawal stays withdrawn.
92 db.run("UPDATE asset SET approved_by_id = NULL, approved_by_name = NULL, approved_at = NULL, approved_via = NULL WHERE kind = 'mls'");
93 migrate(db);
94 expect(rows().find((r) => r.kind === "mls")!.approved_by_name).toBeNull();
95 });
96
97 test("a copy started from the sample starts at 0 approved", () => {
98 const db = fresh();
99 seed(db);
100 const me = { role: "user", id: "u1", name: "me" } as const;
101 const made = launchListing(db, me, { fields: { ...SAMPLE, address: "9 Copy Way, Portland, OR 97214", features: SAMPLE.features.join("\n") } });
102 expect(made.ok).toBe(true);
103 expect(made.ok && made.value.assets.filter((a) => a.approval !== null)).toHaveLength(0);
104 });
src/sample.ts +3 −3
@@ -136,11 +136,11 @@ export function seed(db: Database): void {
136136 const done = db.query("INSERT OR IGNORE INTO seeded (name) VALUES (?1)").run(s.name);
137137 if (done.changes === 0) continue;
138138 const made = insertListing(db, { id: "sample", name: "Sample listing" }, s.listing, s.photos, s.headshot);
139 // The first sample's MLS text is approved, so the preview's property page shows words, not a 404.
139 // Alder Lane opens finished: every asset signed off by its sample agent, never by a person's door.
140140 if (s.name === "sample")
141141 db.query(
142 "UPDATE asset SET approved_by_id = 'sample', approved_by_name = 'Sample listing', approved_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), approved_via = 'page' WHERE listing_id = ?1 AND kind = 'mls'",
143 ).run(made.id);
142 "UPDATE asset SET approved_by_id = 'sample', approved_by_name = ?2, approved_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), approved_via = 'sample' WHERE listing_id = ?1",
143 ).run(made.id, `${s.listing.agent_name} (sample)`);
144144 }
145145 // A sample planted before statuses existed is marked sold once too; after that it is the owner's to change.
146146 for (const s of SAMPLES) {
src/walkthrough.test.ts +5
@@ -87,6 +87,11 @@ test("every step of the section works, with the labels it names", async () => {
8787 expect(sampleHtml).toMatch(new RegExp(`role="tab"[^>]*>${tab}[ <]`));
8888 }
8989
90 // The sample opens finished, signed off by its sample agent; the Boise one is not.
91 expect(homeHtml).toContain("Ready to send");
92 expect(sampleHtml).toContain("Approved by Morgan Reyes (sample)");
93 expect(sampleHtml).not.toContain("Draft — not approved");
94
9095 // 2. Read an asset and its check; the Boise sample is the one that catches something.
9196 expect(sampleHtml).toContain("Fair Housing: clear");
9297 expect(section).toContain("27 Birchwood Court");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs