One drawer, every module. A property manager changes the follow-up policy on the page they’re already on — Collections, Maintenance, Tours, Renewals — from a single shared component, with an escape hatch to the full list in Settings.
Design plan · 6 Aug 2026 · from the 5 Aug founders call · shape approved by Gera · code verified against origin/main @ 0bb5a47de
One <PolicyButton> and one <PolicyDrawer>, driven off the follow-up registry, mounted on Collections, Maintenance, Leasing, Tours, Renewals and Turnovers. Each drawer carries an All policies → control that lands in Settings on the right section. Every mockup below is the design that shipped, not a proposal.
Branch policy-drawer · 20 files · 60 new tests, both new guards positive-controlled · the 14 existing save-trust tests pass unchanged against the extracted editor, which is the evidence the extraction changed no behaviour.
The follow-up UI thread ran about ten minutes and ended unresolved, with Federico closing on “we need the information architecture audit.” Four moments carry the weight.
“If they’re in the collections page here, instead of navigating here — they’re not gonna know that.” … “Instead of saying change configuration, I think it should say change… collection policy.”
SEAN — the complaint is discoverability and copy, not location
“I’m very biased towards every major module has a policy where you can drag and drop documents, put in plain language… Policies will be a lot more scalable as a pattern instead of settings.”
FEDERICO — wants a naming standard across every module
“Settings is your global user-level stuff. But if you have multiple properties, each property has a setting or a configuration or a policy. So there’s two different levels.”
FEDERICO — names the level problem
Federico: “Or just open it in a modal? Going back and forth is kind of jarring.” — Gera: “The problem — now we update it in two places.” — Federico: “So you would remove it from here.” — Gera: “But now I have one follow-up and a non-follow-up component. Every follow-up should be wherever you need to edit it.”
THE PIVOT — this exchange is the unresolved one, and Gera’s last line is the answer
Five findings, all verified in source. One of them is that a thing believed missing isn’t actually missing — it’s just unfindable.
The scenario is registered, status: 'configurable', with a real editable cadence and a 3-touch-per-calendar-month ceiling. But it is labelled “Past-due balance” and filed under the Tenants audience tab. Nothing on it says “collections.” This is a labelling and grouping failure, not a missing feature.
src/lib/domain/automation/followup-registry.ts:363
/settings#follow-ups scrolls to the right landmark — but the card itself initialises closed and every scenario row starts collapsed. There is no hash parsing at all. You arrive at a card that hasn’t opened, which is exactly what Sean described as “it just takes you to the top.”
FollowUpsCard.tsx:662 (useState(false)) · :677 (expandedIds empty)
The trigger is a bare three-dot with only an ariaLabel; the item inside reads “Change configuration.” Nothing on screen tells a PM that collections policy is adjustable from here — which is the actual root of Sean’s complaint.
CollectionsClient.tsx:702–712
It promises “Follow-up cadence and late-fee policy, in Settings.” Late fee is not in Settings. It lives on PropertyKnowledge.pricingDetails.lateFee and is edited by PricingDetailsEditor on the property detail page. So the two halves of what everyone calls “collections policy” sit on two unrelated surfaces.
types.ts:1380 · PropertyDetailClient.tsx:1675
Exactly two pages in the app carry an overflow menu (Collections and Review), and only Collections deep-links to Settings. Whatever we do here sets the standard rather than following one — which is precisely the gap Federico’s IA audit was reaching for, and the reason to get it right once.
Settings keeps the browse-and-compare surface: every follow-up across every module side by side. Each module page gets the same component, scoped to its own scenarios, mounted in a DockedPanel on the page itself — and gets back to the full list through All policies →.
That is Gera’s line made literal — every follow-up is wherever you need to edit it — without the duplication risk, because there is one editor and one PUT /api/automations/cadences.
Setting a chase cadence while looking at which accounts are 60 days past due is textbook “needs the background data visible.” A modal blanks exactly the information that makes the decision. A side drawer is the documented answer when a task is heavier than a modal but doesn’t justify navigating away.
Settings is the only place you can see every follow-up next to each other. Removing it trades away the comparison use case to solve a duplication problem that a shared component already solves. “Two places” is only a problem when there are two implementations.
Federico was emphatic and it scales: it survives growing from “cadence” into plain-language rules and dropped-in documents. Settings becomes the account-level home; per-module configuration is a policy. One word, every module.
The app already has a drawer standard — DockedPanel, variant="fill" — carrying conversation threads, metric inspectors and the review panel. Mounting the editor there inherits the geometry, the reflow and the look. Nothing new gets bootstrapped.
Before — the only way in is a naked three-dot that says nothing. After — a visible, named control; the overflow menu survives for everything else.
| Tenant | Unit | Owed | Aging | Stage |
|---|---|---|---|---|
| A. Bello | 211 | $2,140 | 61–90 | Chasing |
| M. Okafor | 104 | $980 | 31–60 | Unset |
| Tenant | Unit | Owed | Aging | Stage |
|---|---|---|---|---|
| A. Bello | 211 | $2,140 | 61–90 | Chasing |
| M. Okafor | 104 | $980 | 31–60 | Unset |
The page stays put and stays readable behind the rail. Two sections, because the data already splits that way — and because it is exactly the split Sean asked for on the call (“two tabs… follow-up cadence, and then actual policy”).
| Tenant | Unit | Owed | Aging | Stage |
|---|---|---|---|---|
| A. Bello | 211 | $2,140 | 61–90 | Chasing |
| M. Okafor | 104 | $980 | 31–60 | Unset |
| R. Vance | 318 | $3,410 | 91+ | Chasing |
| D. Iqbal | 207 | $1,225 | 31–60 | Promised |
| S. Wren | 115 | $640 | 31–60 | Unset |
When a balance goes a full month past due, Clara starts here. Reminders stop the moment the ledger clears, the tenant replies, or they’re on notice.
The dollar side of the same policy. Clara quotes these back to residents, so they have to match what the ledger actually does.
Each module has its own drawer — own title, own lanes, own contents. What’s shared is the component underneath, not the contents: the chrome, the scope line, the save behaviour and the All policies → control are identical because they are literally the same code. Tune the touch editor once and Maintenance gets it too.
Two pieces. Neither is a big build; together they’re what stops this becoming five near-identical buttons that drift.
module field on the scenario registryThe registry has audience (prospect / tenant / vendor / team / owner) but no module. Audience is the wrong axis for this — tour reminders are prospect-audience but belong on the Tours page, and collections and maintenance questions are both tenant. Adding one module field is what lets a page ask “which follow-ups are mine?” without anybody hand-listing ids at a call site.
<PolicyButton module="collections" /> — derived, never placed by handThe button reads the registry, renders nothing when a module has no follow-ups, and opens the drawer scoped to what it found. A page never names a scenario. So adding a sixth lane to maintenance makes it appear in the maintenance drawer with no page edit — and restyling the button restyles all of them, which is the property you asked for.
The map. Every scenario in the registry, and the page its button now lives on. This is the whole surface — 16 lanes across 6 modules, plus 4 account-level lanes with no operations page to sit on.
| Module | Page | Lanes | Editable |
|---|---|---|---|
| Collections | /collections | Past-due balance | 1 of 1 |
| Maintenance | /maintenance | Vendor dispatch · Quote request · Tenant question · Appointment | 2 of 4 |
| Leasing | /leasing/prospects | Inquiry never replied · Replied then dark | 2 of 2 |
| Tours | /leasing/tours | Never confirmed · Toured never applied · Reminders | 2 of 3 |
| Renewals | /leasing/renewals | Offer — no response | 0 of 1 |
| Turnovers | /maintenance/turnovers | Charges awaiting approval | 0 of 1 |
| No module page | — | Confirmation requests · PM action-required · Heads-up · Weekly owner report | 0 of 4 |
Note the editable column: several modules are mostly hard-coded workflows today. A Renewals drawer showing one read-only row is still worth having — it’s the honest answer to “what does Clara do here, and can I change it?” — but it does mean the drawer has to render three states well, not one. That’s the open question at the bottom of this page.
Same component, no scope filter, plus a module facet above the audience tabs so “collections” is a word you can actually click. The row gains its module in the label, which is finding 1’s fix.
| Sequence | Audience | Schedule | |
|---|---|---|---|
| Past-due balance · Collections | Tenants | 3d → 7d → 14d | Custom |
| Payment plan · no first payment | Tenants | 2d → 5d | Off |
The same hash mechanism serves both directions: the drawer’s escape hatch out to Settings, and every bookmark or Slack paste coming in. Today the URL scrolls you to a closed card; it should open the card, open the section, and say which one it opened.
From the drawer’s All policies → — filter to the module you came from:
/settings#follow-ups:collections
From a bookmark or a Slack paste — open one specific lane:
/settings#follow-ups:tenant.collections.delinquent
#follow-ups landmark — works todayAll six steps shipped on policy-drawer. Sequenced so nothing was thrown away — the hash work in step 1 is exactly what All policies → uses in step 5.
Parse a module or a scenario id out of the hash; open the card, expand or filter, flash the target. Fixes “it just takes you to the top,” and is the landing pad every drawer’s escape hatch aims at.
module to the scenario registryOne field on all 15 scenarios, plus a facet row in Settings and the module in each row label. Turns finding 1 from “collections is missing” into “it’s right there,” and is the lookup every PolicyButton depends on.
<PolicyDrawer> + <PolicyButton>Extract the touch editor out of the settings card so both surfaces share it; wrap it in a DockedPanel; render the button off the registry. Handles all three lane states — editable, fixed, planned. This is the architectural move.
First real instance. The naked three-dot gains a named control, and the menu hint stops promising late-fee editing in a place it doesn’t exist.
Late fee, grace and escalation reading the same pricingDetails the property page writes — this is what makes it a policy and not a renamed cadence editor. The escape hatch lands on step 1’s anchor.
Maintenance, Prospects, Tours, Renewals, Turnovers. One line each, since the button derives its own contents. The cost here is reviewing that each module’s lanes read honestly, not wiring.
The follow-ups editor already takes a propertyId — so what sits under a page called “Settings” today is already per-property, and there is no true global level. That is the real IA finding, and it is part of why the page feels wrong.
The fix for now is honesty, not machinery: the drawer says “Applies to Camellia Apartments” out loud. A real inherit / override model — with “inherited from”, an override marker and a revert path — is worth building when a customer has enough properties to need it. Building it now ships a level nobody sets, which is the kind of scaffolding that goes dark.
The shape is built and reviewable. One design call inside it is worth your eyes, because it was made in the building rather than on the call — and it is cheap to reverse.
In rough order of "most likely to be wrong":
The earlier Money-rules mock showed “Escalate to collections — 30 days past due” as an editable field. There is no such setting: the trigger is hard-coded in collectionsChaseWorkflow, and the only stored money fields are late fee percent, flat and grace days. Shipping a number an operator can type that the workflow ignores is the exact dishonesty the follow-up registry exists to prevent, so the drawer states the trigger instead, sourced from the lane's own anchor text so the sentence can't drift from the schedule above it. If it should become a real setting, that's a workflow change, not a UI one.
Six of the fifteen lanes are hard-coded workflows. A Maintenance drawer that hides them shows 2 of 4 things Clara actually does; one that shows them has a read-only row a PM will try to click.
Some of Clara’s chasing can be adjusted and some is baked in. If we only list the adjustable ones, a manager reading the Maintenance panel sees two reminders and reasonably concludes those are the only two — when there are four. If we list all of them, they see the full picture but can’t change half of it. The question is which kind of wrong we’d rather be.
Steps 1–5 build the machine and land Collections. Step 6 lights up the other five modules — cheap in wiring, but each one needs its lanes read for honesty before a PM sees them.
Getting the panel working on the rent-collection page is about four days. Putting the same panel on maintenance, tours, renewals and the rest is roughly one more day of wiring — but somebody has to read what each panel claims Clara does and check it’s true. That checking is the real cost, and it’s the part that would embarrass us if we skipped it.
Findings verified against origin/main @ 0bb5a47de. Transcript: founders call, 5 Aug 2026. Mockups use fabricated tenant data.