Sean's 2026-08-30 pass: contact first and carrying the name, the activity log above the automated follow-ups, and an em dash on every field nobody has collected yet.
30 Aug 2026 · branch fede/prospect-detail-ux · captured against propflow-stage · at 87c1d0331a+local
PR #6645 · live preview (poke at it) — this page is not a replacement for the preview; it is the half that carries the before and survives when a preview cannot be built.
Data note. Every name and balance below comes from propflow-stage, which is anonymized by construction (ADR-0097/0110 — phones in the impossible +1000 NPA, emails @example.test). The identical capture against prod would carry real tenants' names and balances and is not safe to attach anywhere, which is why the generator refuses to run against one.
Captured from a second dev server running origin/main, so this is the page Sean was narrating rather than a description of it. Three things to read.
Prospect info's reading order. Lead first, then Tour, and Contact last — at the bottom of the card, below everything the lead said. It carries an email and a phone and no name. Sean: “I think it makes sense to have like the contact information first and then this is like their contact card, so I think it should say their name here as well… name, email, phone and it should go above the like this information here.”
Which fields exist depends on what was captured. This lead happens to have a bedroom count, a move-in and a target unit, so the Lead section looks full. A lead who gave none of those renders a shorter card — the rows simply vanish, so a PM cannot tell “no preference” from “nobody asked”. There is no Pets row at all, on any lead. Sean: “it should have just a dash here. That way you can see what other information needs to be collected.”
The sidebar leads with the follow-ups card, and the Activity Log sits under it. Sean: “the activity log… is more like high-level summary, so I would just swap the location of these two.”
On this section's auto-generated provenance caption. It carries a ⚠ reading “that is the current tip of origin/main… it is showing today's main, which already carries the change.” The first half is true and is the point; the second half is a false positive. server-commit.ts warns whenever a BEFORE server runs main's tip, a rule written for the case where an artifact is regenerated after its PR merged. This PR is open and unmerged, so main's tip is exactly what its before must be. The proof is in the captions themselves: this frame stamps e090645455 and the AFTER frame below stamps 87c1d0331a — two different commits, so they are not “the same code”. Reported as a follow-up rather than fixed here; this PR is a prospect-detail UX change and the guard is shared capture infrastructure.
Asserted in the captured DOM — Prospect info · Contact · CURRENT STATUS · MOVE IN · PHONE · Activity Log. The labels are shouted because `Field` renders them `text-xs uppercase` and these assertions match rendered innerText, which carries the CSS transform. Asserted on the BEFORE so the AFTER's additions are a real change and not a page that failed to load.
The same route against the same anonymized row. Read it against the frame above.
Contact now opens the card, above Lead and Tour, and its first row is NAME. That knowingly repeats the page header's <h1> — it is the ask, and a contact card without the contact's name is not a contact card. It is not prospectDisplayName's output, which falls back to the email and then the formatted phone so the header always has a title; a phone number under a label reading “Name” would state a phone as a name. The page resolves a real name through the canonical isPlaceholderName and hands the card null otherwise, which renders the dash.
PETS is a row now, reading an em dash on this lead because nothing was ever captured for them. It comes off the inquiry's existing leadFacts.pets and renders verbatim (“1 small dog”, “No”) when there is one — that field's own type docblock forbids normalizing it. UNITS SHOWN is likewise present-with-a-dash rather than absent.
The Activity Log leads the sidebar, with the follow-ups card under it and Signals last. The log answers what has happened; the follow-up card answers what is queued next, and a PM opening a lead is catching up before they are dispatching.
Nothing here is a fabricated fallback. The em dash is the only stand-in — never N/A, Unknown or TBD — and a test scans every rendered field value for those.
Asserted in the captured DOM — Prospect info · Contact · NAME · EMAIL · PHONE · Lead · CURRENT STATUS · UNIT TYPE · MOVE IN · PETS · INTERESTED UNIT · UNITS SHOWN · Activity Log. NAME / PETS / UNITS SHOWN are the three rows this PR adds to a card that previously rendered none of them on this lead — asserting them is what makes the picture evidence rather than decoration. All shouted for the same innerText/uppercase reason as the BEFORE.
The only other prospect this anonymized source exposes to the org, at a different stage (lease_signed rather than approved). Same shape as the first BEFORE — Contact at the bottom without a name, no Pets row, follow-ups above the log — which is the point: the two asks are about the page's structure, not about one row's data.
On this section's auto-generated provenance caption. It carries a ⚠ reading “that is the current tip of origin/main… it is showing today's main, which already carries the change.” The first half is true and is the point; the second half is a false positive. server-commit.ts warns whenever a BEFORE server runs main's tip, a rule written for the case where an artifact is regenerated after its PR merged. This PR is open and unmerged, so main's tip is exactly what its before must be. The proof is in the captions themselves: this frame stamps e090645455 and the AFTER frame below stamps 87c1d0331a — two different commits, so they are not “the same code”. Reported as a follow-up rather than fixed here; this PR is a prospect-detail UX change and the guard is shared capture infrastructure.
Asserted in the captured DOM — Prospect info · Contact · CURRENT STATUS · PHONE · Activity Log
Contact-first with the name, the full field set with dashes where nothing was collected, and the Activity Log leading the rail — on a lead at a different stage, against the same code. The change is structural, so it lands identically on both.
Asserted in the captured DOM — Prospect info · Contact · NAME · Lead · PETS · UNITS SHOWN · Activity Log
They prove that Contact moved to the top of Prospect info and gained a NAME row, that PETS and UNITS SHOWN now render on leads where they previously did not exist at all, and that the Activity Log now precedes the automated follow-up card in the sidebar — on two leads at two different funnel stages, against the same anonymized rows before and after.
They do not show the two cases the anonymized source contains no row for. First, a fully sparse lead — no bedroom count, no move-in, no target unit — which is the case Sean was actually describing; both prospects this org exposes carry all three, so the “every row still renders, each as a dash” claim is pinned in tests (ProspectSummaryCard.dom.test.tsx and prospect-detail-sean-layout.test.tsx) rather than in a picture. Second, a lead with pets on file: both rows here have leadFacts.pets unset, so the frames show the dash and the tests show the verbatim value.
One thing deliberately absent from the diff and therefore from these frames: the Tour section still only renders when there is a tour. The dashes are for qualification facts — things a PM chases the lead for — and a tour is an event, not an uncollected fact; Tour: When — would state a gap that is not a gap.