| Status | Item | Phase | Evidence | |
|---|---|---|---|---|
Phase 1Data trust — merged to main 08-31 (#6647)— | ||||
| Shipped | 1.1 Funnel ↔ prospect-page tie-out | Phase 1 | #6647 | |
Sean video 1 · numbers bug Dashboard funnel (30d) said 35 prospects, prospect page said 47 — four surfaces computing DIFFERENT numbers correctly. Fixed with one canonical countLeasingFunnel() whose three axes (window basis · terminal stages · counting unit) are required named arguments pinned in one constant; adopted at dashboard card, leasing tab, prospects page, digest and snapshot gauges. A drift test walks every surface’s real entry point over one fixture and fails on any hand-rolled count. | ||||
| Shipped | 1.2 Application count — 9 applied, of which 3 signed | Phase 1 | #6647 | |
Sean video 1 · numbers bug Signed is now counted as a NESTED subset of applications (applied AND signed in-window) exactly as Sean described, so the conversion is a rate of a real population. Terminal (signed/rejected) leads stay in the denominator by construction. | ||||
| Shipped | 1.3 Prospect tour/application not rendering on the profile | Phase 1 | #6647 | |
Sean video 4 · data-join bug The broken tour join is repaired in load-prospect-detail.ts (he had two inquiry records; the page subtracted the wrong one) with a sibling-pinned-tour regression test — MERGED. Signed-in prod evidence captured 08-31: authenticated against propflowai.co (/api/auth/me 200; the signed-out negative control 302s to /login and renders none of the data), the prospect detail page renders the Tour card (Sat, Aug 29 · 3:15–3:30 PM), the Application card, and a leasing rail reading Inquiry · Tour · Application reached. The deployed build is confirmed to contain the fix (/api/health commit is a descendant of the #6647 merge). Honest scope of that proof: the witness rows were rewritten in prod on 08-31 before capture, and an A/B of the real loader pre- vs post-fix against the live rows is now IDENTICAL — no prod row anywhere still reproduces the defect. So the screenshots show the page working, not the fix discriminating; the causal proof lives in the sibling-pinned-tour regression test in CI, positive-controlled against main. | ||||
| Shipped | 1.4 Leasing-tab summary tie-out | Phase 1 | #6647 | |
Sean video 5 · numbers bug The leasing tab reads the same canonical count as every other surface — ties out by construction. | ||||
Phase 2Prospect detail UX — merged to main 08-31 (#6645)— | ||||
| Shipped | 2.1 Contact card first, complete, with the name | Phase 2 | #6645 | |
Sean video 2 Contact moved from last to first and gained a Name row (deliberate duplication of the page header — “this is like their contact card, so it should say their name here as well”). | ||||
| Shipped | 2.2 Activity log above automated follow-ups | Phase 2 | #6645 | |
Sean video 2 Sections swapped — the activity log is the high-level summary and reads first. | ||||
| Shipped | 2.3 Every field always rendered — em dash when uncollected (+ Pets) | Phase 2 | #6645 | |
Sean video 3 Move-in, unit type, pets, interested unit, units shown all hold their slot with an honest “—” so the PM sees what still needs collecting. Pets was added off leadFacts.pets, rendered verbatim. The dash asserts nothing — it is not an N/A-style fabricated value. | ||||
Phase 3Consistency + polish— | ||||
| Shipped | 3.1 Funnel explanation → hover glyph | Phase 3 | #6646 | |
Sean video 1 The inline explanation strip became an info-icon hover (Phosphor, arsenal pattern). Merged 08-30. | ||||
| Shipped | 3.2 One color vocabulary on the prospects page | Phase 3 | #6646 | |
Sean video 1 One dot vocabulary across the page, token-driven. Merged 08-30. (The DATA TABLE’s colors were not covered — that residual is item 4.5.) | ||||
| Shipped | 3.3 Date filter on the leasing tab | Phase 3 | #6688 | |
Sean video 5 The leasing tab needs the same date-range control as the home dashboard. #6647 already taught the tab to read a period window, so what remains is the picker control — build it as ONE component with item 4.8’s start+end range picker, not a second control. | ||||
Phase 4Round 2 (Gera 08-31 + videos 6–9) — lanes running · drive-to-merge granted 08-31— | ||||
| Shipped | 4.1 Dashboard 5 applicants vs prospect page 6 — SadieKate Ball missing | Phase 4 | #6685 | |
Gera hand-check · numbers Gera walked the lists: SadieKate Ball is on the prospect page count, not the dashboard one. Hypothesis to verify FIRST (unconfirmed): the canonical count folds co-applicants into one HOUSEHOLD (dashboard) while the table lists records — if she is a co-applicant this is the open household-vs-record product decision surfacing, not a defect. ANSWERED by Gera 08-31: count RECORDS — “I would count co-applicant people as two. So I would just say six… but I would make that consistent throughout.” SHIPPED #6685. countingUnit is record across all four folded surfaces (canonical constant, prospects page, reporting digest, metric-snapshot gauges); the prospects list stops folding co-signers and renders one row per person, keeping the tile-equals-rows invariant at the new unit. Prod, read-only, 2026-08-31 (Camellia, propflow-prod): 30d inquiries/tours/applications/signed moved 46/23/5/3 → 50/24/9/3; the five rows the fold hid are four flagged co-signers — one of them SadieKate Ball, the person named in the original report — plus one non-face member of a signed application. One deliberate exception: the digest’s leasesSigned stays LEASE-grained (deduped on the application group). It is the only owner-facing row named after the lease, and two people co-signing one lease is one lease — un-folding it would have emailed an owner “Leases signed: 2” for a single lease. Caught in review, fixed, and pinned by a test plus a non-vacuous control. | ||||
| Dismissed — Fede owns | 4.2 Duplicate prospect records not merged | Phase 4 | — | |
Thread, after video 8 “Didn’t get merged correctly” — Fede is actively working the merge bugs in this area. Tracked here so it closes; not double-worked by this effort. | ||||
| Shipped | 4.3 Top cards must filter the data table on click | Phase 4 | #6687 | |
Gera 08-31 Clicking Tours / Signed Lease on the top cards should switch the data table to that stage — today only funnel-chart clicks do. One click behavior everywhere. | ||||
| Shipped | 4.4 No 2×2 fold on mobile for dynamic card groups | Phase 4 | #6687 | |
Gera 08-31 On mobile the cards wrap 2×2 and clicking makes the layout jump. Spec corrected by Gera 08-31: NOT single-column. Cards that SHARE A PREVIEW (share tabs) stay as ONE FULL ROW when compressed to mobile — don’t split tab groups that share previews, so clicking never makes the layout jump. Apply the same shared-preview-stays-one-row rule everywhere else it occurs. | ||||
| Shipped | 4.5 Funnel ↔ data-table stage colors still diverge | Phase 4 | #6687 | |
Gera 08-31 + Sean Inquiries purple on the funnel vs blue on the table; tours light tan-blue vs tours-confirmed green; applicants light blue; application review orange. One stage→color token set shared by funnel, cards AND the data table — extends merged 3.2, which unified the page but not the table. | ||||
| Shipped | 4.6 Chart date adjustment fully dynamic — data AND averages | Phase 4 | #6690 | |
Sean video 6 + Gera SHIPPED #6690. Cause was precise: TrendView owned the brush range in its own useState and said so in a comment — the rail narrowed the PICTURE and nothing else. Lifted to MetricInsight, which already owns every other range fact; a new viewport-slice module restates the CHART’S OWN placement rule so the table lists what the plot draws. Review caught that the fix reached three consumers and missed the fourth — the card’s biggest number still named a bucket clipped off the plot. Fixed at the cause, with a capture on the end-handle axis the first two captures were blind to. | ||||
| Shipped | 4.7 Historical-average toggle line on occupancy | Phase 4 | #6690 | |
Sean video 6 SHIPPED #6690. Unweighted mean of every reading on record, labelled on screen with its real span (92.4% · 102 mo · Jan ’18 – Jun ’26) and a cadence derived from the record’s median gap rather than assumed. Below three readings the toggle renders DISABLED naming the count it has — no back-fill, no carry-forward, no average of one. For Gera/Sean: the window is ALL history, the most conservative honest choice; a trailing-N is one pure function away. | ||||
| Shipped | 4.8 Month filter becomes a RANGE — start + end (AppFolio-style) | Phase 4 | #6688 #6707 | |
Sean video 6 SHIPPED #6688. Root cause located precisely: ObservabilityChart skipped any series with fewer than two points, and the inspector is months-only — so one month = one bucket = a chart that drew nothing (not even the empty state, which keys on length 0). Fixed in two layers: resolvePlotGranularity steps to the coarsest finer grain that still yields a line (a month of daily data plots as weeks), and a genuine one-reading series renders as a centred mark. One shared MonthRangePicker now serves the charts AND the leasing tab (3.3), composed from the existing DateRangePopover ladder rather than a second list, with tests pinning that every offered row maps to the RIGHT persisted token. Review caught a real defect en route — the split view pooled every property’s points, which would have captioned a monthly drill-down row as “Mar 1, 2026”; the grain is now asked of each series and OR-ed. Open judgment call: Gera removed literal Start/End field rows on 2026-07-15 (“we don’t need to say Start and End”), so that dated decision was NOT reversed — the range semantics were fixed and the picker put where it was missing. If literal fields were meant, that is a small follow-up. Now answered — SHIPPED #6707 (round 3, option C3-2). Gera picked “the fields ARE the folder tabs”, which settles the judgment call without reversing the 2026-07-15 decision: there are still no literal Start/End field rows — the two folder tabs are the control, each always carrying its live value. MonthRangeCalendar’s chevron year-stepper is deleted (no parallel path) and replaced by MonthRangeFolder: a year column running to 2000 under the canonical thin scrollbar (2000 was twenty-six clicks away before), and picking a From month auto-advances to To, so which endpoint the next click sets is never implicit again. The silhouette is the real RowSwitcherTabs geometry extracted into FolderTab and consumed by both, rather than a second shape that agrees today. Scope note: this changes the CUSTOM-range surface, so the leasing tab (3.3) is deliberately unchanged — it passes allowCustom={false} because its numbers are recounted server-side by PeriodKey. Review round 1 caught two real defects (a same-year re-anchor that scrolled without re-anchoring, and three tests that would have reddened main every Jan–Mar); round 2 was 🟢 LGTM. | ||||
| Shipped | 4.9 Drag tooltip too thin | Phase 4 | #6690 | |
Gera 08-31 SHIPPED #6690. The pill was 4px 8px of 10.5px type in a saturated chip; it now uses the app’s floating-surface chrome (shared popover-styles radius/border/shadow, card background, 11.5px tabular, a notch). Tokens only, asserted by value. | ||||
| Shipped | 4.10 Chart dragging is finicky | Phase 4 | #6690 | |
Gera 08-31 SHIPPED #6690. Six named defects, not “made it robust”: setPointerCapture threw on a stale pointerId leaving the rail armed-without-capture; no lostpointercapture handler, so alt-tab stranded the control; preventDefault() suppressed focus so the handle ignored arrow keys; a 6px grab band; a dead track; and a minimum-span window whose own handle boxes covered its pan target. The brush value math and ARIA contract are untouched. | ||||
| Shipped | 4.11 Safeguard events on the conversation are illegible | Phase 4 | #6686 | |
Sean video 7 A fair-housing guard event showed with no explanation of what was flagged or why — “it just says it’s about fair housing and then said we manually sent a message”. Guard events must be self-explanatory: what fired, why, what happened next. (The specific flag was a false positive — steering misread — Fede is retuning the guard itself; this item is UI legibility.) | ||||
| Shipped | 4.12 “Application submitted” on the conversation chain-of-record | Phase 4 | #6686 | |
Sean video 8 · product call Raul’s post-tour application link was CORRECTLY suppressed (he had already applied) — but nothing on the conversation shows the application, so a correct suppression read as a broken chain. Sean wants it on the record; Fede is unsure it is a conversation event. APPROVED by Gera 08-31 (decision b88149827 resolved): render “Application submitted” as a system event card on the unified conversation timeline (precedent: call events + WO checkpoints already render as non-message events). | ||||
| Shipped + residual | 4.13 Clara read “afternoon” as “evening” and refused a bookable tour | Phase 4 | #6689 | |
Sean video 9 · behavior bug Fabian said he is available tomorrow AFTERNOON; Clara replied the office closes after five and “we’re unable to accommodate an EVENING tour”, pushing him to Saturday — twice. HALF SHIPPED #6689 — read this before calling 4.13 closed. The prospect is Fabiënne, not Fabian; found by scanning prod for the phrase. Shipped: a real tool defect. A part-of-day ask (“tomorrow afternoon”) has no single minute, so parseTimeToMinutes returned null and the slot search read that as “that time isn’t recognized” — handing Clara outside_office_hours, which her prompt treats as licence to decline using office hours. A new pure part-of-day-offer module narrows to the named window and, critically, separates “the office is closed then” from “the office is open but nothing is left” — telling someone we are closed when we are open is a different false statement, not a milder one. NOT shipped, and deliberately so: the prompt edit was WITHDRAWN. The stored tool_result shows check_availability returned office hours 09:00–17:00 and 27 open slots running 9:00 AM to 4:45 PM for the exact day asked about, four seconds before Clara wrote that the office closes at five and an evening tour was impossible. The model had the afternoon in hand and refused — so the tool fix, though real, would NOT have saved that conversation, and it is latent on the sibling get_tour_slots path instead. Four escalating faithfulness levels failed to reproduce the model behaviour, so no unproven prompt change was shipped. Leading residual hypothesis, worth its own item: an operator’s hand-typed message is stored author.kind: 'clara'/manual_outbound, while the teammate-turn rail is gated on author.kind === 'human' && kind === 'pm_inbox_reply' — so a human’s words reach the model wearing Clara’s own voice. Blast radius is every manual send. Also unmodelled: adaptive thinking + low effort (the bearer provider sends neither). E2E deferred to post-deploy per the runbook. Closed done-with-residual on 2026-08-31, on a Fable decision held under reversal (receipt fefe43dc2): with no fail-before there is no shippable prompt change to chase, and the authorship bug is an agent-loop workstream rather than a rider on a UI round. Both residuals are refiled as their own Fast Follows cards so neither depends on this page being re-read: the unreproduced model half and the manual_outbound authorship bug. | ||||
| No items match. | ||||
| PR | Title | State |
|---|---|---|
| #6646 | Prospects: funnel explanation becomes a hover glyph; one dot vocabulary (3.1 + 3.2) | Merged 08-30 |
| #6647 | One canonical leasing-funnel count, adopted at the four compared surfaces (1.1–1.4) | Merged 08-31 |
| #6645 | Prospect detail: contact card first, activity log above follow-ups, every field rendered (2.1–2.3) | Merged 08-31 |
b88149827 application-submitted event — RESOLVED yes (Gera 08-31). Household-vs-record — ANSWERED in-thread: count records, dashboard says 6, consistent everywhere (the b88117854 page was raised from another session’s ledger; the answer is recorded here and in 4.1’s row). b88118039 window basis — still open.