Making every page fast and private

Plan for the page-speed and company-separation work: every page loads only your company's data, straight from the database, a page at a time, and nobody at one company can ever see another's.

22 Sep 2026 · DRAFT — waiting for Gera's approval · reviewed by two separate AI reviewers (Opus and Fable)

The one decision needed now

Approve this plan?

Yes, start (recommended): it first fixes the vendor data leak, then finishes the prospects speed-up. Anything that touches your live database still comes back to you first.

Change something: tell me which step to add, drop or reorder.

Where each page stands today

Checked against the actual code, not from memory. “Tested” means a test fails if the page ever starts reading another company's data.

PageOnly your dataTestedLoads a page at a timeNeeds a new database index?
ProspectsYesNoFix waitingAlready has one
TenantsYesYesNoNo: small enough to sort in memory
PropertiesYesNoNoNo: short list
TurnoversYesNoNoYes: grows forever, stored in random order
Work ordersYesYesNoYes: grows forever
Tours / calendarYesNoNoYes: it asks for a date range
ConversationsNoNoNoBuilt, switched off: needs you
VendorsFix waitingFix waitingNoLater

What the traffic data changed

The plan, in order

  1. Fix the vendor data leak. A safety fix, not a speed one, so it stays first. A manager at one company, opening a vendor shared with other companies, can see that vendor's jobs for your other customers. The fix is written; one of its tests is accidentally calling the real database.
  2. Measure why the dashboard is slow, before changing it. Some of last month's slowdown may have come from our own recent changes. Compare the old and new code side by side and count the database round trips. That tells us whether it's the code, the data or the platform.
  3. Speed up the dashboard and the shared stats request. The fix follows what step 2 finds. One fix, three of your top pages faster.
  4. Finish the prospects speed-up. Your #2 page. Loads the first 100 leads instead of all of them. Two known fixes, plus a screenshot before it ships.
  5. Decide what a conversation is, then the conversations index.needs you Your #4 page. The index is built and switched off, but switching it on today would fill it with chats filed under buildings, the shape behind the Western Slope duplicates. Settle “one conversation per person per company” first (see the section below); nothing is built until you answer.
  6. Stop pages loading every company's building list. One small change on three pages, copying what already works on renewals.
  7. Build one shared testing tool, then add tests for properties, calendar and turnovers, so “only your data” is proven, not assumed.
  8. Design indexes for work orders, tours and turnovers.needs you Lower priority now: these pages get little traffic. A written proposal only; nothing is created.

Is the data model right? The Western Slope question

Raised in the 21 Sep standup: one Western Slope prospect became four conversations, the match on a street address failed, and the wrong fee was quoted. Western Slope has one shared phone number across many homes, and prospects ask about several homes in one chat.

The idea that untangles it: every message answers three separate questions. Who is it (the person)? Which door did they use (a line or inbox, pointing to a company or building)? And what is it about (a home, a lease, a repair)? At a one-building customer all three answers are “that building”, so filing everything under the building worked. With one door and many buildings, it doesn't.

RecordFiled under todayRight?
Prospect / leadThe person, with a company-wide list for the pageYes and since this morning a lead keeps every home discussed
TenantThe building, linked to the personYes a lease is at a building
Work order, turnover, tourThe buildingYes they're about a unit
ConversationThe buildingNo the one person-level record still filed under a building

August's “one conversation per person” work grouped chats on screen, but still per building, so the flaw stayed underneath.

What it looks like for Western Slope

Western Slope has five buildings sharing one phone number. Today, a prospect who asks about three of them gets three conversations, one per building. After the change they get one conversation, filed under the person at Western Slope, with the buildings shown as tags (“asked about: Building 1, Building 3, Building 5”). Every building they mention is added to the list.

What a “building” and a “phone line” are

What changes and what doesn't

Rule of thumb: a record about a building stays under the building; talking to a person goes under the person and company.

RecordFiled underChanges?
TenantsBuilding (a lease is for a unit)No
Work ordersBuilding (a repair is in a unit)No
RenewalsBuilding (renewing one lease)No
Turnovers, toursBuilding (one unit)No
Prospects / leadsAlready the person, with a list of homesNo (check: four homes asked about = one lead, not four)
ConversationsBuilding → person + companyYes, the only change

Work orders, renewals and leads already point to their conversation by its ID, and that link doesn't change: several of them can point to the same conversation. A resident at Building A with a leak, who also asks about a bigger unit at Building B, has one conversation. It shows the repair (filed under A), the renewal (A) and the new inquiry (B). Building pages look exactly as they do today. If the same person also rents from a different company, that's a separate conversation, and neither company sees the other's.

The order it would be built in

  1. You and Fede decide (b90034167). Nothing is built before that.
  2. New messages go into one conversation per person per company. We find the person by phone or email and record which line they used. Tested first with real messages on a test company, because Clara replies through this path.
  3. Conversations keep a list of the homes discussed, so Clara quotes the right home's fees instead of guessing from a street address.
  4. Merge the existing duplicate conversations (this changes live data, so it comes to you first), then switch on the conversations speed-up.

Proposal (a draft for you and Fede to test, not decided): a conversation belongs to one person at one company. The line or inbox is recorded on each message, and buildings become a list on the conversation, the way leads now carry a list of homes. It's per company, not global, so one company never sees another's chats, and it fits the conversations index, which is already split by company.

Your call: this is already on your decisions page as b90034167 (“OK to build one conversation per person per company?”). Answering it there is the ruling. It touches Clara's live messaging, so bring Fede in.

How the new database layouts would work

The pattern, already live on the prospects page. Every row gets two extra labels: whose data it is and the order the page shows it in. The database can then hand back “this company's newest 50” in one step, instead of us loading everything and sorting it ourselves. The cost follows what's on screen, not how much is stored.

PageStatusGroup labelOrder label
ProspectslivePLIST#<organizationId><lastActivityAt>#<inquiryId>
Conversationsbuilt, offCLIST#<organizationId><last activity>#<conversationId>
Tenantsno index neededPROPERTY#<propertyId>OCCUPANCY#<id>, sorted in memory
Propertiesno index neededPROPORG#<organizationId>PROP#<propertyId>
Work ordersneeds onetoday PROP#<propertyId>today WO#<PMS id>: not a date
Tours / calendarneeds onetoday PROP#<propertyId>today TOUR#<id>: random order
Turnoversneeds onetoday PROP#<propertyId>today TURNOVER#<id>: random order

For the last three, the candidate follows the prospects shape: company or building as the group label, a real date (last updated, tour start, move-out) as the order label. Final field names are part of the design step, not decided here.

The choices that make or break each one

Learned the hard way on prospects:

Two decisions that can wait

What the reviews corrected

Full working notes: ~/.claude/jobs/loop/1fdb267c/PLAN-DRAFT-2026-09-22.md · Handoff: HANDOFF-2026-09-22-1700Z.md

PropFlow Docs