The rest of the hold page names who receives the message

The hold approval page still told the approver a renter would get the message — including on the page to the handyman. Before and after, two dev servers against propflow-stage.

27 Oct 2026 · branch held-page-recipient-wording · captured against propflow-stage · at 45dd6e6563a+local

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.

1 · BEFORE, on main — the heading says handyman, the sentence below says renter

The automatic work-order page to the handyman, held because Maintenance is supervised here (source: 'maintenance_vendor_sms'), with Clara's own draft check flagging it so the warning banner renders.

Read the two lines against each other. The heading says Text to a handyman or vendor — that is #23001, merged. The banner below it still says approving sends it to the renter. Same hold, two different people.

Asserted in the captured DOM — Text to a handyman or vendor · approving sends it to the renter exactly as written below · Would go to. Both lines asserted on main at once — the contradiction is a measurement.

BEFORE, on main — the heading says handyman, the sentence below says renter
Provenance. Captured from commit 8248e9342c0, read from the worktree the server was started from (/Users/miniclaw/code/PropFlow/propflowai-wording-before) — the server could not report its own commit, so this is the weaker of the two sources. That commit is already merged into origin/main.
Sections 2–8 and notes

2 · AFTER, on the branch — both lines name the same person

Same hold, same draft, same flagged-draft banner. The sentence now comes off the hold's own record through heldRecipientPhrase — the mid-sentence form of the ONE derivation (heldMessageRecipient) that the heading and the approval queue's "to" column already read. That is the half of Fede's 2026-10-27 inspection this PR closes; #23001 closed the heading.

"a handyman or vendor", not "the handyman", on purpose: the stored source records only that the text is not for the resident, and the assigned tech is the company's own handyman on a work-order page and an outside firm's on a relay. Naming one would be a fabricated fact half the time. A source this build cannot place names nobody — "approving sends it exactly as written below" — rather than falling back to the renter.

Asserted in the captured DOM — Text to a handyman or vendor · approving sends it to a handyman or vendor exactly as written below · Would go to. The new sentence is in the captured DOM in the state the screenshot shows — not just in the diff. "renter" is gone from the page.

AFTER, on the branch — both lines name the same person

3 · CONTROL — the resident twin of that same work order still says a resident

The resident-facing half of the same work order (maintenance_resident_sms, the "your work order is open" notice), flagged the same way. It read "a resident" before and it reads "a resident" now: only the holds whose recipient was being misnamed moved.

Asserted in the captured DOM — Text to a resident · approving sends it to a resident exactly as written below. A resident hold is untouched by the change, asserted on the branch.

CONTROL — the resident twin of that same work order still says a resident

4 · BEFORE, on main — an approved handyman page is waiting on "the renewal"

The same kind of hold, already approved. On main the badge reads Clara will send the text (after quiet hours if it is late), unless the renewal has moved on — on a page about a leaking faucet. That arm is not gated on the hold's resume, so every held text got the renewal's name for it.

Asserted in the captured DOM — unless the renewal has moved on. The old renewal-specific sentence is asserted on main, on a hold that has nothing to do with a renewal.

BEFORE, on main — an approved handyman page is waiting on "the renewal"
Provenance. Captured from commit 8248e9342c0, read from the worktree the server was started from (/Users/miniclaw/code/PropFlow/propflowai-wording-before) — the server could not report its own commit, so this is the weaker of the two sources. That commit is already merged into origin/main.

5 · AFTER, on the branch — one sentence, no module in it

Reworded rather than branched on the source, because the mechanism it describes is module-neutral: three different actors withdraw a held text before its send (system:renewal-moved-on, system:draft-superseded, system:round-trip-settled). Conditioning the clause on the source would have added a second source-keyed table to say one true thing.

Asserted in the captured DOM — unless it is no longer needed by then. The reworded sentence on the same hold on the branch; the word "renewal" is no longer on this page.

AFTER, on the branch — one sentence, no module in it

6 · The reject box, opened — the note is kept for whoever asks, not for "the resident"

Clicking Reject opens the reason field. Its label used to read kept on file in case the resident follows up later — the wrong person on a handyman page, a team ask or an owner alert. This component knows the hold id and the channel, not who receives it, and the reason is kept for whoever asks later whatever role that is. So the label says what it is for and names nobody, which is also what /review's own reject box already says.

Asserted in the captured DOM — kept on file in case there are questions later. Asserted after the click, in the state the screenshot shows — the field only exists once Reject is pressed.

The reject box, opened — the note is kept for whoever asks, not for "the resident"

7 · At phone width — the longer sentence still reads

The sentence grew by the length of a role name; the longest this change can produce is the renewals reviewers. At 390px it wraps inside the warning banner and nothing below it moves.

Asserted in the captured DOM — approving sends it to a handyman or vendor exactly as written below. The same sentence, asserted again at 390px — a responsive claim nobody has to take on trust.

At phone width — the longer sentence still reads

8 · Dark mode — the same two lines

Nothing in this change adds a colour, a token or a surface; it is copy on an existing Banner tone="warning". The dark capture is here to say that out loud rather than to be taken on trust.

Asserted in the captured DOM — approving sends it to a handyman or vendor exactly as written below. The same sentence in dark mode — no token was introduced or redefined.

Dark mode — the same two lines

What the captures prove, and what they do not

Before and after are the same routes against the same stage rows on two servers, so the copy is the only thing that moved — and the before sections would have failed the run if main had not actually said "the renter" and "unless the renewal has moved on".

Not reached here: the eight other recipient roles, the held-email path (where a resident's maintenance reply would have read "a prospect" before this change), and the hold whose stored source this build cannot place — which DROPS the clause entirely rather than falling back to "the renter". Those are covered by HeldMessageView.recipient-copy.test.tsx, which renders the real view per role and is red on main.

All three holds are synthetic fixtures on the stage test building, phones in the impossible +1000 area code. Nothing here can reach a person.

PropFlow Docs