Four surfaces printed four different numbers for the word "prospects". They were not computing one number badly — they were computing four different numbers correctly, and none of them said which. Captured as a real before/after: the BEFORE comes from a second dev server running e090645455 — the commit this branch departed from — against the same anonymized stage table, so the two halves differ by this diff and nothing else.
30 Aug 2026 · branch fede/leasing-funnel-canonical-count · captured against propflow-stage · at 18a6344834+local
PR #6647 · 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 e090645455, the commit this branch departed from. The funnel card here counts over a window the reader cannot see or change — the layout provider pinned it at 12 months — and it counts on the stamped basis: prospects windowed on createdAt, the DDB row-WRITE clock that the ADR-0035 backfill refreshed table-wide, with terminal stages dropped and each applicant counted per person rather than per application.
None of those three choices is stated anywhere on the page, which is the actual defect. Sean, reading prod: "these numbers are not tying out to the summary numbers on the prospect page… right now this says 35 prospects. But here it says there's been 47."
Measured on this exact commit against this exact table: 34 / 15 / 9 / 3. Asking it for a 30-day window and a 12-month window returns the same four numbers — the second half of the defect, and the reason the control below is not cosmetic.
Asserted in the captured DOM — Prospects · Tours · Applications · Signed. The four stage rows, on the pre-change card. The NUMBERS are the claim and they are carried by the pixels — asserting a specific count here would pin the fixture rather than the behaviour, since the stage table moves with the prod mirror.
The same pre-change server, one click away. This page has always counted on the activity basis — Sean's own 2026-07-16 call, "a lead who inquired months ago but is messaging today is a live lead" — with terminal stages kept and a couple's co-signed application folded to one household.
Both pages are right. Neither says what it is counting, so a reader comparing them concludes the data is broken. That is the trust cost this PR is paying down: not a wrong number, an unstated one.
Asserted in the captured DOM — Prospects · Tours · Applications. Same stage vocabulary on a page whose counts come from a different basis — which is the whole point of putting the two captures side by side.
The same route on this branch. Every surface now calls countLeasingFunnel with all three axes as required, named arguments — windowBasis: 'activity', includeTerminalStages: true, countingUnit: 'household' — so a surface that varies an axis has to say so in code, which is what makes it say so on screen.
The tab also gains the dashboard card's own date control instead of the provider's pinned 12 months, so the window is a thing the reader chooses rather than a thing they have to know.
The same measurement as the BEFORE section, on this commit and this table: 46 / 22 / 5 / 3 — against 34 / 15 / 9 / 3 two sections up. Applications falls from 9 to 5 because the numerator now nests: Signed used to count leases that had already been dropped from the Prospects population they came from, so the funnel's last stage could out-number its first.
Verified beyond these pixels: a read-only run against propflow-prod has all five entry points returning 46 / 22 / 5 / 3 at a 30-day window, with zero rows counted at a stage outside the cohort they were drawn from. The same script against this stage table returns the same numbers, which is what makes this anonymized capture representative rather than merely safe.
Asserted in the captured DOM — Prospects · Tours · Applications · Signed. The stage rows on the post-change card. Equality across surfaces is proved by the tie-out in the PR body, which is a stronger instrument than a screenshot for that particular claim — this capture is what the reader SEES, and the two are deliberately different kinds of evidence.
This page barely moves, and that is the result: it was already counting the way the investigation recommends, so the work was bringing the other three surfaces onto its reading rather than changing it. What changed underneath is that its fold, its window and its count are now the same functions the dashboard calls — foldProspectsToHouseholdRows, passesLeasingFunnelWindow, countLeasingFunnelCohort — instead of a parallel implementation that happened to agree.
The seam is real and is fenced: this page folds locally because its rows keep the household handle its table reads, so it composes the two halves of the canonical path rather than calling the one-shot. leasing-funnel-canonical-count.drift.test.tsx asserts that composition equals the one-shot call.
Asserted in the captured DOM — Prospects · Tours · Applications. Same page, same shape as the BEFORE two sections up — the evidence that this half did not have to move for the four to agree.
Proven by the pixels: that two surfaces on origin/main render the same four stage words over different populations with no on-screen statement of which; and that the leasing tab now carries a window control where it previously carried a pinned, invisible 12 months.
Not provable by a screenshot, and proved another way: that the four surfaces now return EQUAL counts. A picture of two numbers that match is weak evidence — they could match by luck at one instant. The PR body carries a read-only tie-out that runs all five entry points over one prod row set at one window and prints them in a table (46 / 22 / 5 / 3, equal across all five), plus a nesting check by row identity showing zero rows counted at a stage outside the cohort they came from. That is the instrument for this claim; these captures are the instrument for what a reader sees.
Deliberately absent: a capture of the prospect detail page's restored tour. Its evidence is a server render of the production component on production data — two rows, one person, six shared conversations — and it belongs in the PR body rather than here, because reproducing it needs the prod rows and this artifact is captured against the anonymized stage mirror by construction (the generator refuses a production data source).
Two axes are still open with Gera (decisions b88117854 and b88118039). Each is a required named argument chosen in exactly one place, so a different answer is a one-line edit, not a re-derivation.