A work order's tenant messages stop rendering under the work order's URL. The link now opens that tenant's own conversation page — their whole history — with the exchange about that job highlighted and scrolled into view, and the old URLs redirect there. Captured as a real before/after: the BEFORE comes from a second dev server running origin/main.
30 Aug 2026 · branch feat/entity-conversation-unified · captured against propflow-stage · at cb47a83218
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 origin/main. This is the URL Gera held up: /maintenance/work-orders/CAM-1184/conversation?with=tenant — the resident's own text thread, framed as a property of the ticket. The trail reads Work orders / CAM-1184 / conversation, Back returns to the ticket, and the URL teaches that a work order has a conversation. His words: "by having the backslash conversation, I think I've been misrepresenting what a conversation is."
The highlight machinery already worked here — the job's messages carry the band and the legend counts them — but the page is scoped to one thread under the record, not the person's whole history.
Asserted in the captured DOM — Work orders · CAM-1184 · conversation · highlighted. The trail crumbs pin that this reader is hosted under the WORK ORDER's url space — the framing this PR retires — and "highlighted" pins that the band/legend machinery predates this PR (what's new is where it renders, not that it renders).
The same work order's detail page on this branch, after clicking the tenant card's View messages link. It no longer goes to the record-owned page: it lands on /tenants/<id>/conversation?regarding=CAM-1184 — the tenant's own conversation page, whole history, with the messages about this job banded and the page arriving scrolled to the band rather than at the bottom (the capture is the viewport as the page settles, not a full-page render — where it sits is the claim).
The trail now reads Tenants / <name> / conversation and Back returns to the person. The query spelling is ?regarding= — the same word the stored messages already carry (regardingLabel: "CAM-1184"), so the URL and the data speak one vocabulary.
Asserted in the captured DOM — Tenants · conversation · CAM-1184 · highlighted. "Tenants" is the load-bearing crumb — it proves the click LEFT the work order's url space and landed in the person's. "CAM-1184 · N highlighted" is the legend on the landing page, i.e. the exchange lens survived the navigation.
The BEFORE section's exact URL, requested against this branch. It no longer renders a page: the server redirects to the tenant's conversation with the same exchange highlighted, so every ?with=tenant link in Slack history, shared messages and bookmarks lands on the new page forever — and the address bar teaches the new shape (Next's redirect() with replace semantics, verified in the served payload: NEXT_REDIRECT;replace;/tenants/<id>/conversation?regarding=CAM-1184;307).
The redirect target is derived from the work order the route just scope-checked — never from raw query text — and a pre-spine row with no tenant page falls back to the ownerless reader carrying the same lens, so no old link ever narrows to a worse read.
Asserted in the captured DOM — Tenants · conversation · CAM-1184 · highlighted. Same assertions as the click journey, reached through the OLD url — the accept-old half of the migration, captured rather than claimed.
A resident with more going on: multiple threads across channels, three work orders, fourteen messages — ten of them about the bathtub-drain job CAM-2478. The merged read shows everything, in one flow; the lens only says which part is about this job. Nothing is taken away, which is the difference Gera asked for when the old filter toggle died: "be able to see the overall context rather than having to kind of filter off."
This capture also shows the boundary definition this PR ships — exactly the messages stamped as regarding the job — pending his call on decision b88097742: on real prod data, the messages BETWEEN two stamped ones usually belong to a different work order for the same tenant (most multi-job tenants have jobs open simultaneously), so a continuous block would regularly paint another job's messages as this one's.
Asserted in the captured DOM — Tenants · conversation · CAM-2478 · highlighted. The legend names the job and counts the banded set inside a merged, multi-thread read — the state that exists on no page before this PR.
Proven: the click journey leaves the record's URL space and lands in the person's; the old URL redirects rather than rots; the exchange lens survives both paths; the band renders inside the whole-history read; and the viewport captures sit AT the band, which is the scrolled-into-view claim (the arrival scroll targets the first painted band — fullPage: false on purpose, so where the page settles is what the picture shows).
Not provable here: an absence — that no second link scheme survives — is carried by the type system (WorkOrderThread no longer admits 'tenant', so emitting the record-owned tenant URL fails to compile) and by the drift guard that bans the old builder call from the work-order page. And the boundary of the highlighted exchange is deliberately the open decision (b88097742, raised with prod measurements) — this PR ships the recommended definition and confines a different answer to one span computation.
One thing a reader may notice: the vendor/handyman "View messages" link still points at the work order's own page. That is the declared PR 2 — /vendors/[id] has no conversation surface at all today, so the person-owned read there is real construction, not a link swap.