Conversation thread standard

The Messages direction, decided. One thread for every conversation: the renter on the left, Clara and the team on the right, the time under each run.

Building — final tweaks on staging  2026-10-16

Nov 3: retro, why the list keeps getting noisy again, and the one fix

Why we keep playing whack-a-mole on the conversations list

Live in production, Nov 3 evening  Measured, prototyped, built as three PRs, backfilled and checked on the real pages the same day.

26
of Situs's top 50 rows were pasted notices on the day of a notice (Oct 25–26). 12 at the moment of the complaint, 7 right now.
6
changes to what makes a row, in 27 days, by two founders. Each one redefined "noise". None measured the pasted notices.
0
rows at Western Slope come from a staff blast. Their team uses AppFolio's real mass send, which the list already ignores.

What the noise actually is

At Situs the team (mostly Courtney) pastes one pest-control notice into tenant threads one at a time, 7 to 25 threads over a few hours, English to some tenants and Spanish to others. AppFolio records each paste exactly like a normal one-to-one staff text. Nothing on the message says "mass". Only the same text showing up across many threads gives it away. In the last 30 days: 43 notice waves, 28 sent as real mass sends, 8 pasted, 7 half and half.

Courtney pastes a notice into 18 threads→ AppFolio: "staff text", same as any reply→ our sync copies the label as is→ list clock: "substance was sent" → 18 rows jump to the top

The six attempts, and why the blast was still there the next morning

WhenWhoWhat it called "noise"Why the pasted notice still showed
Oct 7GeraAnswered threads hidden by the Sent-as hide; rolled back to cold threads onlyNot about blasts at all; the paste counts as a live staff send.
Oct 18FedeToo many filters and columnsCleanup of the page, no change to the clock.
Oct 22–23FedeMass texts tagged by AppFolio as bulkThe paste is not tagged. 0 of the 12 offending rows carried the tag.
Oct 26FedeAnything we sent; sort on the write-in only (option B)Worked for blasts, but also buried Clara's real sends. Fede was unhappy with that side effect.
Oct 28GeraOnly mass sends, follow-ups and confirmations; any "message of substance" counts againA pasted notice is a staff text, so it is "substance" by definition. The blast came straight back.
Nov 2Gera(open) a "Replied" filter on top of the same clockSame clock underneath; the blast still sets it.

The pattern. Every attempt defined noise by a label (AppFolio's tag, our message kind, "ours vs theirs"). The paste carries the wrong label at the source, so every label-based rule lets it through. Three different rules went live in six days and this page still says the Oct 26 rule is live; production has run the Oct 28 rule since. Nobody measured the untagged paste until today, and the QA for each change looked at tagged blasts only.

Five kinds of noise the measurement found

NoiseWhere, how muchIn the proposal
Pasted staff notices, untaggedSitus: up to 26 of top 50 on notice dayYes: clustered as mass at sync time
AppFolio automatic notices ("request submitted", maintenance reminders)Western Slope: 10 of top 50Yes: already tagged "notice" by us, drop from the clock
AppFolio's "thank you for your interest" auto-reply, stored as a staff textSitus: 26 rowsYes: a one-line mapping fix at sync; not a blast
Call transcripts: one caller makes three rows in 72 secondsSitus, top 15No: separate, small; group a caller's calls
Clara's own first-reply template (one-to-many by text)Both orgsExcluded on purpose: Clara's sends are never "mass"

The decision (Fede, Nov 3)

Decided 2026-11-03 (Fede)  "This is just a constant flow of things that are happening in real time."

What the page is for: a live feed of everything the company says and hears (calls, texts, emails), newest first, one person per row. Fede uses it to spot-check Clara; owners use it to watch their team. It is not a to-do list: no "needs attention", no "waiting on them", no sinking of answered threads. That is why rules borrowed from inbox tools kept misfiring here.

How a paste is recognized: see the harness results below. Usage (30 days): 614 views by PropFlow staff, 23 by five customer people, so today this is a PropFlow QA tool and this fix is the whole job.

How a paste is recognized: three ways, tried on real rows (Nov 3 evening)

A local harness ran all three on a read-only copy of 30 days of Situs and Western Slope texts. The honest test set is 37 pasted copies that are proven pastes because a tagged AppFolio mass copy of the same notice exists. Staff texts only; Clara's templates, AppFolio notices and call transcripts are excluded by kind.

RankApproachSimplicityProven pastes caughtReal one-to-one texts wrongly flaggedSitus top 50, pasted rows left (today → after)Western Slope
1, recommendedNear-match: same text with small edits, same company, 3+ threads in 24 h46 lines, no model, decides as each text arrives29 of 370notice day 27 → 3; complaint instant 12 → 0; now 7 → 0no change
2Model check on the candidates123 lines plus a model call per candidate; offline only36 of 37227 → 0; 12 → 0; 7 → 02 rows move
3Exact match, word for word33 lines, no model21 of 37027 → 11; 12 → 7; 7 → 5no change

Caveat: arrival order was taken from message timestamps; the real sync brings texts in batches, so the arrival-time test is approximate. Nothing was written, sent or committed; the harness stays local.

Prototype on real rows (Nov 3 night, local, nothing written)

The near-match detector wired into the real list code and replayed over 30 days of Situs and Western Slope rows in memory. Top 50 before and after, side by side.

SitusWestern Slope (control)
Staff texts re-marked as mass (= backfill dry-run count)1198
Threads whose list time changes526
Rows leaving the top 50, now / at the complaint instant6 / 170 / 0
Threads leaving the default list356
Top 10unchangedunchanged

Live in production (Nov 3, 5:30 PM MT)

Live — backfilled — checked on the real pages  Three PRs merged (#24982 detector, #24989 re-mark verbs, #25020 live wiring + backfill + standing check). Fix to live behavior, no switch. Backfill run on Fede's yes, Situs then Western Slope.

SitusWestern Slope (control)
Staff texts re-marked as mass by the backfill119 (78 threads)8 (8 threads)
Rows whose list time changed45 (16 moved earlier, 29 left the default list)6 (all moved earlier, none left)
Default list, last 30 days95 rows → 8991 → 91
Top 15 on the pageunchanged (the pasted rows sat at ranks 36–40 tonight; 17 of the top 50 this morning)unchanged
Standing check, "top-50 rows placed by a pasted notice"5 → 00 → 0
Second backfill runplans nothingplans nothing
Situs conversations list before the backfill, 95 rows
Situs before 95 rows in 30 days.
Situs conversations list after the backfill, 89 rows, same top 15
Situs after 89 rows, same top 15; the pasted-notice rows are gone from recency.
A Situs tenant thread after the backfill: the Nov 2 pest inspection notice is still in the thread
A re-marked thread (Nicole, Situs): Courtney's Nov 2 pest-inspection notice is still in the thread, nothing lost.
Western Slope conversations list after the backfill, identical to before
Western Slope after identical to before: 91 rows, same order.

QA pass on production (smoke login, headless): Situs list loads, 89 of 95+ rows, top 15 identical to before; Western Slope list identical before and after; a re-marked tenant's thread opens and still shows the pasted notice; stored rows read back as mass texts with our "inferred" marker and the PMS's own label kept; a second backfill run plans nothing. From now on the third copy of a notice pasted within 24 h files as a mass text on arrival and the first two are re-marked; the standing check (scripts/ops/measure-untagged-blasts.ts) reports "top-50 rows placed by a pasted notice" per company, healthy = 0.

Follow-ups filed, not blocking: #25032 (the tick's lookback can replay a company's whole mirror history when old rows never file), #25033 (a re-mark can be missed when copies land seconds apart in one tick; plus carried-over nits). Not done: grouping one caller's calls within minutes into one row, and AppFolio's "thank you for your interest" auto-reply still stored as a staff text.

What the fix looks like, built once

sync pulls a staff text→ same text in 3+ threads of this org within a window? → mark as mass (inferred), earlier siblings too→ clock, Sent-as, thread label all read our own mark→ the row sits where the tenant's last message put it

Scope: 2 to 4 PRs, no switch (it is a fix to live behavior), proof is the production screenshot before and after with the counts above. Western Slope should not move at all; that is the control.

Oct 26: Conversations list, what makes a row (decided: option B)

Conversations list: what makes a row

Decided 2026-10-26 (Fede): option B  "Only show people who wrote in." Added here because this page already holds the list's design decisions (Oct 16 ruling below).

Superseded Oct 28  Live Oct 26 evening (built, merged, backfilled, checked on the real pages). Since Oct 28 production runs Gera's rule instead: the clock is the later of the write-in and the last "message of substance"; mass sends, follow-ups and confirmations do not count. See the Nov 3 retro above.

Default list, last 30 daysBeforeAfter
Situs69 of 100+ rows, mostly staff texts nobody answered96 rows, every one a person who wrote in, sorted on their last message
Western Sloperows lifted by our own sends91 rows sorted on the write-in; 87 threads moved to their real spot
"Show people we messaged with no reply" (extra filters menu)did not existbrings the texted-only people back (Situs: 100+)
A STOP on the threada plain bubble or nothingwhite "Opted out of texts" card (proof: Keith Pitchford, Situs, Sep 30 3:30 PM)

What shipped: the list sorts on the write-in, not on our sends (#22520); per-kind send hides replaced by the one reveal (#22528); the opt-out card (#22501). Backfill run on Situs (494 threads) and Western Slope (452) on Fede's go. Open follow-ups: an automatic AppFolio "Do Not Reply" email still counts as a write-in at Western Slope; a START with no company attached shows "Opted back in" while texting stays blocked (#22577).

Fede's ruling (Oct 26): a row appears or moves up only when a resident or prospect writes to us: a text, an email, a call, or a listing inquiry. Our texts, staff texts and notices stay in the thread but never make or move a row. Edge cases decided the same day:

The pictureOther productsOur numbersDecisionEdge casesHistory

The short version. Every product we checked, AppFolio included, keeps a message we sent off the top of the "needs attention" list. PropFlow does not: any staff or Clara send, including a staff text copied in from AppFolio, jumps the row to the top. That is why 145 of Situs's 185 visible rows are staff texts nobody answered. Fede's rule ("show it when someone external writes in") matches the campaign tools exactly, and in effect matches the AppFolio screen Situs staff use today.

Today's row lifecycle vs "someone external wrote in"

Today (as built)
Any text or email, either direction, copied from AppFolio or sent by us→No conversation yet? Create a row→Staff or Clara send: row jumps to the top→Then 4 hides subtract: system notices · team hand-offs · mass texts (AppFolio "bulk" label only) · follow-ups and reminders

Automated follow-ups no longer move a row (since mid-October). Staff and Clara sends still do. Each hide was added after a complaint.

"Someone external wrote in" (Fede's rule, option B)
Every message is still recorded on the person's thread→Did the person write, call, or send a listing inquiry?→Yes: row created or moved to the top/No (our send): row neither created nor moved

What other products do

ProductWhat creates a rowWhat sorts itWhere bulk sends live"Waiting on them"
AppFolio (our read-only look at Situs, Oct 26)Every text, either direction. One row per recipient, even for bulk. No bulk label."Unread First" (Situs's setting): resident-wrote-last on top. Our sends are marked read and sink.Same list, one row each. Bulk starts from the AI assistant (a customer's own help article: Treaty Oak KB).Grey "replied" arrow, read, below all unread.
BuildiumTwo-way texts in Text Messages (help)Not statedBulk of up to 10 goes out as individual texts (help). Property-wide sends go to a separate Announcements screen, "guard your inbox from sudden spikes" (help).Follow / unfollow only
TextlineAn Announcement opens one conversation per recipient by default (help)Not statedAnnouncements screen; option to auto-resolve the conversations it creates (feature page)Open / Resolved
PodiumNot statedNot statedBulk Messages list (knowledge base). By default skips anyone already in an active conversation (release 25.7)Not stated
IntercomInbound, or a teammate composing. Outbound-section message: "no conversation present until the contact responds" (API docs)"Last activity" or "Waiting since"; after a teammate replies the thread moves "to the bottom" (sorting)Outbound section, "assign replies to"Bottom of Waiting-since
HubSpotInbound. An inbox email we send "will not appear in the All open view until the contact replies" (guide)Oldest/newest (which timestamp: not documented)Marketing tool; a reply to a marketing text "will appear as a new conversation" (SMS help)Separate Sent view
FrontAssigned, mentioned, or new activity (sections)"Newest" (any activity) or "Oldest unreplied" (sorting)Sequences (email), which stop when the person replies (sequences)"Later" section
Help ScoutInbound arrives as Active (help)"Waiting", Active above Pending (waiting sort)No bulk email feature foundPending; flips to Active on reply
ZendeskTicket from requesterNot confirmedNot coveredPending: "waiting on information from the requester" (help)

Heymarket, SimpleTexting and Salesmsg keep broadcasts in their own tab and send replies to the shared inbox (search snippets, unverified: Heymarket, SimpleTexting). Outside property tools the line is the same: Gmail keeps Primary for "people you know" (Gmail Help); the texting industry's CTIA rules call a text conversational only when the "first message is only sent by a Consumer" (CTIA, May 2023). Counter-example: Help Scout's "Waiting" sort does move when staff or an automated workflow emails the customer; Active-above-Pending is what keeps it down (Help Scout).

The pattern: campaign tools create no row; a person composing does. No product puts our send on top of its "needs attention" view. AppFolio never labels bulk; its sort sinks it.

QuestionMost precedentsAppFolioPropFlow today
Does our send create a row?Campaign: no. Composed by a person: yes.Yes, one per recipientYes, for every copied or sent message
Does our send move the row to the top?NoNo (sinks under Unread First)Yes for staff and Clara sends; automated follow-ups no longer do
How is bulk kept out?A separate screen, plus sortingSorting aloneHides by kind, after the fact
"Waiting on them"A status or bottom of listRead, below unreadNo state. A hide of answered threads was tried and withdrawn Oct 7 (below).

Our numbers

Default list, last 30 days. PropFlow production, read-only, Oct 26.

Situs GroupWestern Slope
Rows shown185207
Newest message is a staff text from AppFolio, person never replied1455
Newest is Clara (person replied at some point / never)1 / 095 / 14
Newest is the person1717
Rows already removed by the mass-text hide295—
Today alone34 rows, 29 of them staff sends—
Under Fede's rule (row only when someone external writes in)~30~170

The Western Slope estimate counts a listing-site inquiry (Zillow and similar) as "the person wrote in", so lead conversations stay; the 14 "never replied" Clara rows rest on the same assumption.

Spotting bulk can't be made reliable. AppFolio's message record has no recipient count, template or batch id, only a send-route label ("bulk" or "one at a time"). Courtney's Oct 26 pest notice went to 26 residents: 20 one at a time, 6 as bulk, so 20 rows got past the hide. A "same text to 3+ people" rule would also have caught Kat's three genuine one-to-one prospect texts at Western Slope.

The options, for the record (B chosen)

All three keep every message on the person's thread. They differ only in whether our own sends can make a row exist or put it on top.

What Fede already said that bears on this. Oct 7, withdrawing a hide of answered threads: "let's say I scroll down and I see an older session, even though we followed up today, I still want that session down there. If I click it, the follow-up is there in that thread… it's more like skipped." He objected to hiding an answered thread, not to keeping our sends from moving it; since then automated follow-ups no longer move a row. Oct 16, on this list: no pinned "Needs you" group (section below). That rules out C as a pinned group; it says nothing about C as a sort order or a separate view.

Precedents split evenly: B (campaign tools) and C (person-to-person tools, AppFolio included). No public source covers how phone calls count; Fede decided they count as writing in.

Edge cases

CaseA (as built)B (chosen)C (waiting on us first)What precedents do
Staff one-to-one AppFolio text, no reply (Situs: 145 rows, incl. notices sent one at a time)Row at topNo row until they replyRow, low or in SentAppFolio: row, sunk. HubSpot: Sent view only. Intercom: row, bottom.
Automated follow-up, no replyRecorded; row does not moveRecorded; row does not moveRow sorts lowFront sequences stop on reply. Help Scout's Waiting sort does move.
Clara's first reply to a new listing-site leadRow; top when Clara sendsRow (the inquiry is the write-in), at the inquiry timeRow; drops once Clara answeredAll: the inquiry creates the row. Intercom and Help Scout treat "we answered" as waiting on them.
Resident replies "STOP" to a noticeRow at topDecided: no row; a white "Opted out of texts" event card on the threadRow on topIntercom: only "non-keyword messages" open a conversation (Intercom).
Resident replies "ok" to a noticeRow at topRow at topRow on topAll surface it. SimpleTexting tags it with the campaign (unverified); Textline links it to the notice within a time window (help).
Tenant who only calledRow if the call is logged as activityDecided: row (a call counts as writing in)Row; position depends on whether Clara closed it outNot covered. Only Podium and Knock marketing copy mention calls in the thread (Knock).

History: #17340 (Oct 7, the answered-thread hide withdrawn) · #20915 (Oct 18, list cleanup from the Oct 16 ruling) · #21877 (open: hide copied mass texts by default) · #21962 and #21994 (Oct 23, mass-text hide and the Hide-section options).

Where it stands: walk 5 on real data (2026-10-18)

Every thread of the three live customers (271, production table, read-only) rendered on the chain head and judged screenshot by screenshot. Folds 1–14 are merged and live; fold 15 (work-order card wording) is the last one in the chain.

WalkHeadClean threads of 271
Production, Oct 15before the chain0
Walk 2folds 1–521
Walk 3folds 1–772
Walk 4folds 1–10115
Walk 5 (stricter brief)folds 1–14107 clean + 18 clean by design = 125

What is left on walk 5, by cause:

The rest of walk 5, and what remains by cause

Fixed on the chain since walk 4: the day on stored-date cards no longer lands a day early (#20932, root cause #20933), a caller's cut-off always joins its continuation (#20925), a shared-login writer reads "Team" on both card paths (#20929), the header lists channels from the person's own rows (#20931), and a work-order card never reads "Work order ()" or names its unit twice (fold 15).

Decided

The thread
The thread on a phone, light
Phone, light
The thread on a phone, dark
Phone, dark
The thread on desktop, light
Desktop. Group thread: Maya (renter), Clara, Kat and Jay. Every card is a kind the app already records (table below).
Emails in the thread
Three emails in the thread
Light
Three emails in the thread, dark
Dark

An email is a bubble like any other: the subject in bold, then the body with its line breaks, as the app shows it today. A PDF shows as a chip, the way the app shows attachments today. Our emails carry the small "Email" word; the renter's carry the time only. Desktop.

The app never shortens a long email or folds quoted history today, so the mockup shows the whole body. A "Show more" after six lines would be new: how it would look. It is off in the playground unless switched on.

Defaults, tweak on staging

Fede: "we have enough, start building; I'll do final tweaks on staging." The build ships the left image of each pair as the default.

Staff bubble colour: same as Clara

Staff bubbles in Clara's colour, left, and a lighter tint, right

Default: the same colour as Clara (left). In Messages your side is one colour, and so is the staff side in AppFolio. The line under the run says who wrote it. Right: a lighter tint for staff.

Teammate avatar: tinted initial, photo when one exists

Tinted initials, left, and a profile photo with initials as fallback, right

Default: a coloured initial (left), a photo when one exists. Each teammate gets the same colour every time, from the brand colours, with a white letter. The app already does this for property avatars. Right: a profile photo where one exists, falling back to the initial (Kat shows a placeholder silhouette; Jay has none). I found no staff-photo field in the app's data today, so until one exists every teammate gets the initial.

Action cards: one white card with a Phosphor icon

One card for every event; two weights; a look per category

Default: one white card for every event (left). The icon says what happened. Amber only when someone must act. No card in the table below carries that state today, so nothing is amber yet. Middle and right are the alternatives.

Green "Delivered" tag: separate decision, unchanged for now

Today's green Delivered tag, left; without it, right

Not a default. The tag and its "what Clara did" panel stay as they are in the build. Options for later: drop the tag and open "what Clara did" from the orb panel (recommended), or drop both. Left: today's tag; right: without it. Today the tag only shows on some of Clara's replies, which is why some texts look different from others.

Every card is something the app already records (build against this)
Card in the mockupWhat the app records todayHow it shows today
New lead from ZillowThe lead email / original inquiryLead card
Tour bookedpm_tour_confirmation_emailPurple bell notice, "PM Tour Notification"
Tour movedpm_tour_update_emailPurple bell notice, "Leasing Team Alerted"
Reminder senttour_reminder_smsA Clara bubble labelled "Tour Reminder (Day Before)"
Application sentThe application-sent eventApplication card
Asked Katpm_escalation_emailPurple bell notice, "Forwarded to Property Team"
Kat answered ClaraThe staff-answered eventStaff-answered card
Incoming call, with audioThe call eventCall card with the audio player
Incoming call, no recordingThe same call event without audioNothing: the card hides itself

Removed from the mockups: "Waiting on Jay" (no such record exists) and "Mark handled". "Mark handled" does exist today, on the forwarded-question notice. It stays there unchanged; the thread redesign doesn't add or move it. "Held, awaiting approval" and "Not sent" stay what they are today: small notes on the message itself, not cards.

Play with it

A scrollable copy on localhost: /thread-lab/play. It opens on the decided design, with a switch for each of the four picks and for light/dark, phone/desktop, group/one-on-one and the long-email "Show more". Example: /thread-lab/play?bubble=tint&staff=photo&actions=two&green=on.

The earlier variants (A, B beside the bubble, C names only), the orb-size and tail comparisons, the grid overlays and the pass-3 directions are in Reference: earlier passes at the bottom.

300 real threads through the new look (Oct 16 night)

Fede: "go very, very deep, a few hundred examples, multiple customers." A local harness rendered 300 production threads (JP&Co 185, Western Slope 73, Willows 38, Situs 4) at phone and desktop, checked every row by code (29 planted defects, all caught) and had a judge read 290 of them row by row. Clean: order, sides, day dividers, emails, no console errors.

FixThreads touchedWhere it lands
Orb first on our sender line (Fede, Oct 18: "Orb / avatar first"; superseded the Oct 16 right-end pick)271PR 2 (in flight)
Drop the Text / Email / Call word262PR 2
Header: last activity, flag, filter, thumbs247PR 5
Renter time aligned to the bubble text230PR 2
Cards not yet on this page (staff wrote to Clara, holds, work orders, transfers, paged)162PR 6
Kind words under runs150PR 2
Chevron / Show details on cards131PR 3
Generic "Human Agent" teammate after transfers93PR 6
Delivered / Sent chip84PR 2
Same recording played twice on transferred calls69PR 6
Property-system name in thread copy59PR 7
Real inquiry card first, never a renter text dressed as the inquiry5 (one is the thread Fede opened)PR 4

Not the screen's fault, filed as product bugs: call transcripts stored out of order or with machine audio as a teammate, Clara texting her own number, bot-to-bot emoji loops, a merged prospect + coworker thread, hundreds of duplicate work-order rows after one call.

Every message UI in the app (Gera's ask: one standard, all modules)

Inventory 2026-10-16. Gera: "make sure it's to all message UIs… I made a standard component so it should update all, I'd rec to change that original one." He is right: one bubble component draws every real sent message, and one thread reader wraps it. Rebuilding those two covers eight screens at once.

ScreenDraws messages withWhat the chain does
Conversation page, person page, tenant / prospect / work order / turnover conversation tabs, conversations drawer, mass-send recipient threads (8 screens)the standard thread reader → the standard bubblePR 2 rewires the reader; all eight change together
Review queue: vendor call, collections draft, new-lease and PM confirmation threads (3)the standard bubble, but without the thread reader (no email, call or event cards)bubbles update with PR 2; one PR moves them onto the reader
Ask Clara popup and docked panelits own bubbleone PR onto the standard bubble
Mass-send preview, collections history, work-order dispatch texts, held emails, held messages, agent transcript, calendar snippet (7)plain text or bordered boxes, each its ownone small PR each, in that order of how often staff see them
Loading placeholders (4 copies)copied grey bubblesone PR replaces all four with one

Counts: 20 screens, 3 bubble implementations (one used only by the marketing site), 4 loading-placeholder copies, 7 one-off text renderers. Open from Gera: pin the sender's avatar to the top while scrolling (not decided, not in the build). Tracking issue #20486.

The Conversations list

Fede's ruling (Oct 16, after seeing the options): keep the same page and the same table, no pinned 'Needs you' group, no new layout. Options A, B and C below are rejected; they stay as the record of what was considered. What ships instead: filters cut to search, time range, Channel and Topic; the Handled-by column, Person type, Sent as, the overflow menu, the person-type dots, the 'Clara →' prefix, the grey 'Clara thinks' property chip and every dash are removed; relay addresses read 'via <source>'; the thread standard's tokens and type scale apply. Built as one small PR in the thread chain (#20486), proven on the Vercel preview with real data.

Decided — Oct 16 evening

The bar: a property manager opens this page to see what is happening. Today the list mostly explains how Clara works: which path handled a message, what she guessed, how a message was made. Proposal (A): a Messages-style list. What needs you on top, everything else newest first, one search box, no filters.

Before

Today's Conversations list with the six extra filters open
Today, on Willows test rows (names, numbers and message text replaced). Nine filters, six behind "+6"; six columns; topic and "handled by" icons; grey maybe-chips; dashes. Without the menu · phone.

What is on the page, and what gets used

Last 30 days, the three live customers together: 302 people. Counted read-only from production.

On the pageValuesLast 30 daysKeep?Why
Person type filterAll people, Residents, Unmatched, New message, Reviewed, ResolvedUnmatched 294 (97%) · Residents 8 · New message 40 · Reviewed 0 · Resolved 0RemoveAlmost all one value; two values never used. "New message" becomes Needs you.
Handled by: filter, column, iconsClara handled, Transferred to humans, Office replied in AppFolio, Answered by your team, Stopped by safety guardClara 231 (76%) · Transferred 64 · Office replied 7 · Your team 0 · Safety guard 0RemoveWhere and by which path is plumbing. Who spoke last shows on the row instead ("Clara: …").
Sent as filter and column7 groups (follow-ups, outreach, confirmations, team, hand-offs, replies, work orders), a Hide section, follow-ups hidden by defaultThe default hide removes 4 rows; "replies" is on 283RemoveHow a message was made is an engine detail.
Called filterThe line that was calledOne line per customerRemoveFilters nothing.
Needs a lookGrading flagsStaff only; not countedRemove hereOur quality review, not the manager's.
Inactive inquiriesShow, hideAny inactive property 2 · all inactive 0RemoveNear zero.
Recency2 weeks, 30 days, 90 days, all time30 days by defaultRemoveNewest first already answers "lately"; search covers all time.
Topic: filter, chips, iconsLeasing, Maintenance, Resident inquiry, Vendor, Billing, Renewal; "+2"; "No topic"; dashLeasing 153 · Maintenance 35 · Resident 33 · Vendor 23 · Billing 20 · Renewal 13 · "No topic" 67 · dash 10A: remove. B, C: a menuThe last line says what it is about. Fede: Topic "might be fine".
Channel: filter, icons, ×NCall, Email, Text, VideoCall 183 · Email 95 · Text 21 · Video 3 · ×N on 73Keep, as a wordA product fact. A word, not an icon.
FromName, number, "Clara → name", relay email, role dotName 192 · number 61 · "Clara → name" 49 (29 of them called in themselves) · relay email 12Keep, new rulesWho is a fact; arrows and relay addresses are plumbing.
PropertyChip, grey "Clara thinks" chip, "+N", dashOne customer: dash on 90 of 199, grey chip first on 33, "+N" on 19. Another: 98 of 100 known. The third: dash on 3 of 3Keep, known onlyA guess never shows (rule below).
UnitUnder the nameShown on 38Keep when realNever a placeholder like "Unit GAUNTLET-102" (in the shot above).
When"8h ago", "Oct 8, 2026"Every rowKeep, Messages style
Notes, banner, pagingThree notes, "Older conversations exist…", Show 15, "15 conversations"Every visitRemoveSearch covers all time; the list scrolls.
Top bar and cornerInsights bulb, Inbox, floating Ask Clara orbEvery pageBulb: remove here. Inbox, orb: keepThe bulb opens nothing here (build chain item 20). Inbox is what PropFlow asks of the customer; the orb is how you ask Clara. Neither is part of the list.

Three jobs, most frequent first

JobWhat the list showsToday
1. What needs me right now?A short group on top, each row with the reason in words: "Left a message", "Call didn't reach your team", "Clara handed this to your team".Spread over Person type, Handled by and Needs a look. 54 people (18%) were waiting on the team; 40 of them for more than a week, because nothing ever clears a taken message.
2. Find this person or unitOne search box: name, number, email or unit, every conversation, all time.Search covers only the rows loaded: 15 a page, 30 days.
3. What has Clara been doing?The last thing said, with "Clara:" or the teammate's name in front.A green check and topic icons. No words.

Grounding: the vision ("You see only the decisions that need you"; "Clara shows you what's happening now"); Fede tonight ("do I really need all this?"); a client's leasing team on their old phone system ("I never got a notification that I got a response").

A. Needs you on top Recommended

Option A on desktop
Option A on a phone

Messages with one pinned group. Rows waiting on your team sit on top in an amber box, the reason in words, until the engine sees them answered. Everything else follows, newest first. Each row: who, where, the last thing said, when. One search box, no filters.

B. One list, a word on the row

Option B on desktop
Option B on a phone

The same rows, one list, newest first. A row that needs you carries a "Needs you" word; a switch shows only those; Topic and Channel stay as menus. Simpler than A, but the two rows that need you sit 9th and 13th of 15, off the first phone screen.

C. Slim table, two tabs

Option C on desktop
Option C on a phone

Today's table cut to three columns: Person, About (topic and channel, or why it needs you), Last activity. Tabs for Needs you and All; Topic and Channel menus. It works on today's data, but it is still a database with no words from the conversation, so job 3 means opening rows one by one.

Recommend A. It answers all three jobs on the first screen: what needs you is on top, search finds anyone ever, and the last line says what Clara did. B buries what needs you; C has no words. A needs one new field and one rule, both below. (rejected by Fede; see ruling above)

Option A on a wide screen with a conversation open beside the list
A on a wide screen: the conversation opens beside the list, like Messages on a Mac.
No conversations yet
No conversations yet
Search with no match
Search, no match

Rules for every row

WhoThe name; else the phone number; else a real email. Never "Clara → name", never a listing relay address. No number under a name: it is one tap away in the thread.
WhereProperty and unit only when known: the line or address they contacted belongs to that property, the listing or the PMS record names it, they live there, or they named it and the engine matched it. Several known: the latest conversation's. A guess never shows; the engine settles it from those signals or the cell stays empty. A company with one property doesn't see the property at all.
Last lineThe last message, one line. "Clara:" or the teammate's first name in front when we said it; the person's own words have no prefix; a call shows its length and what it was about. That prefix is the only Clara-or-team mark.
StateNeeds you, with the reason in words, or nothing. No colour without words; no "handled" state.
TimeWhen the last message was sent or received: 6:28 PM today, Yesterday, Wed, then Oct 8.
Empty"No conversations yet. Calls, texts and emails to Clara show up here as they happen." Nothing needs you: the group is gone, no zero. No match: "No one matches “Delgado”." Loading: grey placeholder rows.

When a row stops needing you. The engine clears it, never a button: a teammate replies or calls back on a channel we see, a work order is logged for it, or the person comes back and Clara closes it. Today nothing clears a taken message (Reviewed 0, Resolved 0), and teams often answer from their own phones, which we cannot see. So after 7 days a row drops back into the list, reason words still on it.

Delete list

New data the build needs

Where each item lives (branch fede/thread-chrome)
  • src/components/feature/dashboard/conversations/ConversationsLog.tsx: filters 2741–2913 (Person type 2743, Topic 2754, Sent as 2816, Called 2849, Channel 2856, Handled by 2865, Needs a look 2870–2892, Inactive inquiries 2894–2903); Recency 1042; columns 2428–2665 (From 2431, Property 2506, Channel 2568, Topic 2613, Handled by 2631, When 2660); Sent as column 2693; notes 2993–3007; search 3034; banner 3093; grade flags 1584; inbound-email fetch 1446.
  • src/lib/domain/dashboard/caller-log.ts: Person type values 49; "Clara → name" 148–155.
  • src/lib/domain/conversations/handled-by.ts: lastReplyFromAppFolio 33. sent-as.ts: groups 329, default hide 679.
  • src/components/domain/conversations/ConversationPropertyTags.tsx: grey chip 42, "Clara thinks" tooltip 66.
  • src/app/api/conversations/route.ts: recency 194, calledLine 489, calledLineLabels 1490, stats 1494, threadCountsComplete 1499.
  • src/components/primitives/TopBar.tsx: Insights 93–115, Inbox 122.

Measured with the list's own read functions on the production table, Query and GetItem only (a guard blocked Scan and writes; checked with a planted Scan). The list API could not be called read-only (its company picker writes an audit row), so residents (8) is a lower bound, and role dots, Needs a look and system notices were not counted. The before shot is the real page on the dev table fed 15 Willows test rows (Willows had 2 real people in 30 days). Prototype on localhost: /thread-lab/list?v=A (B, C), ?open=1, ?state=empty or nomatch.

For engineers

Written for pass 2; redone after the pick. Nothing below moves until Fede picks a design. What holds under any of the four: the preconditions (steps 1–4 and 12), the chrome removals (15–21), and the rule that no step gets a switch. The element map and the event steps (7–11) are redrawn to the chosen design's shapes once it is picked.

Every element, mapped (pass 2, to be redrawn)

Current element → the shape it becomes. "Keep" means no visible change.

TodayBecomesWhat changes
Person bubble (blue, right)BubbleSide and fill follow the chosen design. The channel word under every bubble, both sides, always (today inbound has none).
Clara bubble (gray, left) + the February markBubbleClara's face becomes the landing orb (the same component the site uses, 20px). Under the bubble: the time and the channel word; nothing else.
Teammate bubble (gray, initial or photo)BubbleSame bubble as every other sender. First name above when the sender changes; initials on the gray well, no orange.
Label chip: Clara Reply, Follow-up Sent, Checking Back In, Text After Email, From mailboxDetails panelGone from the bubble. "Sent as follow-up" lives in details; the list filter keeps its vocabulary. Two facts survive as facts: a bounced email prints "Email bounced, texted instead" in red under that email; a reply from a shared mailbox names the mailbox as the sender, never "Human Agent".
Segment pill (date · topic · channel, colored dot) + sticky band (the topic tint stopped painting at #3196; the code is dead)Time lineA centered gray time line at gaps, printed when the day changes or after a long silence (one dedupe pass over the final rows). Topic stays in the list; in whole-history view the first row of each conversation keeps its topic and its "open on its own" arrow, which moves into details.
In-flow day chipTime lineSame line; one style.
"Team hand-off" bell card, purple, "Show details"Event line or cardA line when it is a fact: "Clara told the leasing team about the tour." A card when there are words to read: the question Clara asked the team, "Mark handled" on the face, amber "Waiting on your team since 3:24 PM" until answered. No bell, no purple.
"Dana answered Clara" cardBubbleA teammate's answer is a message from a teammate: the same bubble as any teammate, with "replied by email" as its channel word. The card shape retires.
"New lead" purple card and "New inquiry" cardEvent cardOne card: who sent it ("New lead from <source>"), the home asked about, the person's own words. No tint.
"Application sent" cardEvent line"Application sent" with its link; gray, it is history.
Call: player, transfer attempt card, voicemail chips, transcript noteEvent cardOne card per call, always (today a call with no recording gets none): direction, duration, the outcome sentence (amber or red only when it needs someone), a one-line gist of what the caller wanted, player, transcript folded. The "caller hung up after N s" row folds in.
Work-order checkpoint pill, guard-event pill, application dividerEvent lineOne gray line with a glyph. The hairline-pill shape retires.
Tour created / tour refused chipsEvent line"Tour booked Thu 3:30 PM" / "Tour not booked, slot taken" in words, not in blue vs gray.
Blocked draft, "Held, awaiting approval"Event line"Held for review" in red (action required, §18.2) with the glyph; no admin link for customers. The orphan pill folds in.
Email preview card (the "View preview" lens)Bubble + detailsLens removed only after "Open original" exists in details for both directions (today OriginalEmailCard is mounted nowhere). An email is a bubble with its subject line.
Rows copied from the property-management systemBubbleAlready native since Oct 14. Indistinguishable from a row sent through our line; "Sent through AppFolio" and "Open in AppFolio" only in details. A mass text keeps a full bubble with its name line reading "Dana · sent to all residents"; an automatic notice reads "Automatic notice".
Notes typed in the property-management system (staff only)Event line"Dana added a note" + the note; no channel divider of its own.
Clara's meta line (green dot, time printed twice, one in monospace)BubbleOne time, sans face, no dot; delivery shown only when it failed.
Highlight band and emphasized row (violet ground, radius 10)BubbleKeeps its three jobs (which messages belong to a work order, the clicked thread in whole history, where a link lands); a neutral band that fades after two seconds (the work-order filter keeps it while on); never Clara violet around a bubble.
Details panel labels: "Provenance", "Sent by: AI ops agent", "Sent as: Clara Reply"Details panelSentence case, plain words: Sent by Clara · Channel · Sent · Sent through AppFolio · Delivered · Open in AppFolio.
Delivery stamps: red "Not sent: reason", email "Sent <time>"Bubble meta lineEvery failed, not-sent or undelivered state prints red under the message, every channel and source. The success time moves to details.
Grouped messages (merged into one bubble with dashed lines)BubbleStacked bubbles under one name line and one time line: same sender, same channel, within five minutes. iMessage rhythm; each message keeps its bubble.
Purple bells: "Property Manager Paged", "Leasing Team Alerted", "PM Tour Notification", "Office Notified", "Leasing Desk (Text)"Event linePlain verbs with the recipient: "Clara paged the on-call manager", "Clara told the team about the tour", "Clara texted the leasing desk".
"Clara answered staff", "Staff wrote to Clara"Event cardSame grammar: "Dana asked Clara · …", "Clara answered Dana · …".
Reaction (tapback), decision-line info glyphBubbleKeep.

Glyphs (Phosphor, regular, 16px): new lead UserPlus · sent to the team PaperPlaneTilt · application sent FileText · call Phone · transfer ArrowsLeftRight · work order Wrench · tour CalendarCheck · note from the property system NotePencil · held draft PauseCircle.

The PR chain (pass 2; steps 7–11 and 14 redrawn after the pick)

Small steps, one concern each, sizes including tests. No switch anywhere (no per-page divider mode, no setting that keeps the lens, no per-company "show PMS name"): the conversation page, the dock and mass sends change together. Each step ships a Willows before/after; the time-line step adds the byte-identical replay.

#One concernFilesSize
Before the time line
1No label chip: the thread mapping stops passing the label (grouping splits on label, ChatSurface.tsx:1517, so bursts merge once it goes). The chip component stays for the email subject.ConversationThread mapping, 1 testXS
2Channel word on inbound bubbles, inheriting the conversation's channel when the row has none.mapMessage, channel-stamp testS
3"Open on its own" for member rows moves from the pill into bubble details.buildMessageDetails, open-source testS
4One call card per call (keyed by the call), with direction; the early-hangup row folds in.thread-call-events, CallEventRow, 2 testsM
The time line
5Time line: segment markers, day rows and the three synthetic pills become one centered time line with a dedupe pass; segmenter and markers untouched; byte-identical replay. After 2–4.render loop, ~6 testsM
6Delete the dead divider code (segment divider, header formatter, dot colors, channel header icons and labels, tint); keep the topic tints for the list. After 5.ConversationThread, removalsS
Event lines and cards (redrawn after the pick)
7Team hand-off: a line for facts, a card for words. Neutral glyph, act and recipient on the line; "Mark handled" and the amber waiting state on the face of the card.InternalNoticeRow, 2 testsS
8Checkpoint, application and guard rows become the event line.ThreadEventDivider family, 3 testsS
9Four chips plus the blocked-draft orphan pill become the event line.chips, BlockedDraftNoticeM
10Lead and inquiry cards become one card; the staff-answered card becomes a teammate bubble. After #20065 parts 2–3.InquiryCard, LeadEmailCard, StaffAnsweredEventCardS
11The call card absorbs the transfer card and the transcript note. After 4.TransferAttemptCard, VoiceTranscriptNoteM
Property-system rows
12Provider and medium on the row, adapter only: both type trees, the three writers, old rows normalised, link host per provider. No UI.pms-mirror adapters, typesS
13The thread reads the provider label: details rows, mirror sender label, the note row. The eight AppFolio literals go. After 6 and 12.ConversationThread details, mirror-label, StaffContextImportRowS
14Clara's face: the chat chip and ClaraAvatar render the landing orb (move Orb out of the marketing folder into components/brand; one sentence in design-standards §7.1.8).ClaraChip, ClaraAvatar, Orb, design-standards.mdS
Page chrome
15Header: remove the flag and the last-activity line, page and dock.ConversationDetailView, ConversationDetailsDisclosure, 3 testsS
16Menu: copy toasts; Phosphor icon through the menu button's icon prop. After 15.ConversationActionsMenuXS
17"Open original" row in details, both directions.buildMessageDetailsS
18Remove the preview switch: page menu item and the dock tab. After 16 and 17.ConversationDetailView, ConversationsLog, ConversationDockBodyS
19Delete the preview-email path (email card, both fetch hooks). After 18.removalsS
20Top bar: Clara Insights only where a panel is registered.TopBar, DockProviderS
21Sidebar: the onboarding item moves to Settings. After Fede picks.SideNav, GoLiveRailItemS

Two values in the mockups have no app token yet and need one before the chain starts: the dark-mode sent blue #0A84FF (light uses the TENANT_BLUE constant, also untokenised) and the ink option's --r1-ink #1C1B1A, which lives in the landing stylesheet. Already being fixed as bugs, outside this chain: the duplicate staff-answer card (a text row written on an email answer) and staff notices opening a channel divider.

The page around the thread, traced in code

For agents

Pass 4 (2026-10-16). Rulings: Messages is the direction; our side is "me" (Clara and team on the right); who said it stays small under the bubble, never a name on top or to the right; keep the orb; actions based on the landing action card (HeroThread.tsx ACTION_ICON, hero.ts, .r1-bubble--action); design for a group thread, 1:1 as the simple case; keep the per-message Email/Text word; drop kind labels and the green tag from mockups. Harness: sibling worktree ~/.claude/propflowai-scratch-thread-ui-pass4, branch scratch/thread-ui-pass4 off main ce62f94a74, never committed, no PR. Routes /thread-lab (shots), /thread-lab/actions, /thread-lab/play (query v, theme, w, t); npx next dev --port 3077 with the copied .env.local. Shots: Playwright headless, shoot.mjs writes every [data-grid] rect; gridlog.mjs and innerlog.mjs check them. Inventory: 34 event kinds in 12 treatments (ConversationThread.tsx); 93 message kinds in sent-as.ts KIND_LABELS, 76 with a label; green tag = DecisionLineChip, only on clara_reply with a traceId; channel word = outboundChannelStamp() :589. Departures to settle in the build PR: the sent fill here is --color-primary #4F46E5 per the brief, today MessageBubble hardcodes #007AFF; the orb is the landing Orb, frozen. Unverified: the live-app baseline (smoke login returned 401, not retried; Before is pass 3's capture); Apple references are iOS 26 support images; read receipts not modelled; the public nav stays light in the playground's dark mode.

Pass 4, second round (2026-10-16 afternoon, Fede on the localhost playground). Rulings: A2 (sender line right-aligned under our run, time per run; renter time only, left); orb 16; no tail and no squarer corner, 18px on all four corners; B "very strange", set aside; "just a UI refresh, not new functionality": no invented rows ("Waiting on Jay" removed; "Mark handled" exists today on the forwarded-question notice, PmForwardHandled.tsx, and is not part of the thread design); "way too many variants": main flow cut to decided + four picks. Playground /thread-lab/play switches: v (A2 default, A, B, C), orb 16|20|24, staff initial|photo (teammate avatar), bubble same|tint (staff bubble colour), actions same|two|category, green off|on, clamp off|on, theme, w, t. The coordinator asked for staff=same|tint for the bubble colour; that name was already the avatar switch, so the bubble colour is bubble. Tinted initials reuse PropertyAvatar.tsx's palette and hash. Emails copy today's treatment: subject bold, plain-text body pre-wrap (EmailBodyFrame), PDF chip (ChatSurface AttachmentsBlock); no clamp or quoted-history fold exists in the app today. Card-to-kind table sources: pm-internal-kinds.ts, sent-as.ts, thread-application-sent-events.ts, thread-staff-answered-events.ts, thread-call-events.ts, thread-original-inquiry-event.ts.

Rulings in force (newest wins). 2026-10-15 Fede, pass 2 rejected whole: "still using the old Clara logo", "the two rows copied from AppFolio already look different", "why can't it be like iMessage with some clean elements, look at our landing page", "you didn't even look at the new brand". Same night: look at clean designs from top companies and draw inspiration instead of starting from scratch; "do not use AppFolio, Yardi, they're garbage, I want the new clean agentic companies"; "give me a few different designs, not just one". 2026-10-15 Fede: thread is a Frankenstein; standardize human reply, Clara reply, events; remove the header flag, last activity, preview toggle, Clara Insights, onboarding inbox; PMS rows must support Yardi without refactoring. 2026-10-15 Fede: PMS code lives in adapters, core UI never names a PMS. 2026-10-14 Fede (#19161): no "AppFolio text" tag on a mirrored bubble, renders native, system named once in details. 2026-08-29 Fede: the judge flag is a quiet header icon, never a banner. 2026-08-28 Gera: a conversation is a time frame, not a calendar day (18h idle gap, thread-segments.ts). Brand: docs/brand/design-standards.md §18 (twelve principles, six state colors), docs/brand/propflow-brand-book.md, CHEATSHEET.md.

Clara's face: a ruling this page makes, and the one place it departs from the brief's wording. The brief said "the current ClaraAvatar, a flat vector orb". src/components/brand/ClaraAvatar.tsx and public/brand/clara-avatar.svg are the February mark (six dots, two bars, gradient square), the exact thing Fede called the old logo, and the chat chip ClaraChip (ChatSurface.tsx:1156) draws the same dots on an indigo disc. The current brand's Clara is the landing orb: src/components/marketing/page/LandingR1/parts.tsx Orb (conic gradient, 7% blur, sizes 20/32/36/48, scoped under .landing-r1 in landing-r1.css), the face the site gives her in the hero thread next to the label "Clara AI". Every design here uses Orb at 20px and nothing else. Follow-up PR: move Orb to components/brand, point ClaraChip and ClaraAvatar at it, change §7.1.8 (written 2026-08, before Focus Lab) to allow the orb as Clara's face only.

How the mockups were made. A sibling worktree of the app at main 077d3aafb3 (never committed, reaped at the end), the public route /test replaced by a harness page that renders the four designs from the app's own CSS tokens, Suisse Intl, Phosphor and Orb, over one synthetic 15-message thread (_mockups/data.ts). Captured headless with Playwright 1.61 (shoot-harness.mjs: per theme and width a context at 2x with reduced motion, theme set through localStorage propflow-theme-preference, every [data-shot] element screenshotted). One trap for a re-render: /test sits inside the marketing layout, whose .landing-page scope swaps five app tokens (text #111832, Clara violet #6F2AF4, dark bg #000515); the harness restores the app values on its root (.tm-root: --color-text #0F2060/#F4F4F6, --color-clara-primary #6D28D9, dark --color-bg #0A0A0F) and pins the site nav so it never paints over a capture. Round 1 shipped the marketing values before this was caught; check with a pixel sample. Images on this page are downscaled to 1x. Brand strip: the same harness rendering Logo, icon.svg, PropFlowMarkIcon, Orb, ClaraAvatar, ClaraChip from main. Landing crops: headless capture of propflowai.co at 1440 and 390. Apple Messages numbers: connected-region boxes of the bubble color over Apple's support images (measure-bubbles.mjs), colors by pixel sample; ratios, not points. Competitor and moodboard crops: headless page grabs of each company's public site (page-grab.mjs), cropped to the thread, never a logged-in product.

Code, read-only, repo ~/.claude/propflowai. Thread: src/components/domain/conversations/ConversationThread.tsx (SegmentDivider :1320, DayDivider :1414, ThreadEventDivider :1438, ApplicationSentEventCard :1565, StaffAnsweredEventCard :1638, InquiryCard :1713, LeadEmailCard :1883, InternalNoticeRow :2013 with Bell in clara-primary, chips :2099–2227, TransferAttemptCard :1092, VoiceTranscriptNote :2879; channel label map with appfolio: 'AppFolio' :566; details "Sent through"/"Open in AppFolio" :869–886; DOT_COLOR :1011; staff-context rows pushed with channel: 'appfolio' :3630). Bubbles: src/components/composition/ChatSurface/ChatSurface.tsx (LabelChip :776, ClaraChip :1156, InitialCircle peach --color-avatar-tenant-bg, HumanAgentAvatar :1344, grouping splits on label :1517); MessageBubble.tsx (outgoing fill hardcoded TENANT_BLUE #007AFF :125; radius 16/16/16/4). Tokens: src/app/globals.css (--color-bubble-incoming #F2F2F7/#3A3A3C, --color-clara-primary #6D28D9, --color-primary #4F46E5, --color-border #E8E8EB/#232331, dark = html.dark). Landing: HeroThread.tsx, landing-r1.css (.r1-bubble radius 8, 14.7px/22px, padding 10 16; who-row 12px, gap 8; .r1-avatar 20px). Segmentation: src/lib/domain/conversations/thread-segments.ts (markers __SEGMENT__, __DAY_DIVIDER__; never rename, prefix readers in wo-thread-filter, wo-report-backstamp, wo-unified-timeline, collections-message-filter). Mirror rows: lib/domain/pms-mirror/adapters/appfolio-inbox.ts, appfolio-email.ts, lib/domain/conversations/mirror-label.ts, StaffContextImportRow.tsx; door: 'appfolio_text' | 'appfolio_email' fuses provider and medium (lib/data/types.ts:9304).

History (git pickaxe). ChatSurface #1014 2026-05-16; topic-tinted cards + sticky header #2348 06-13; channel pills #3214 07-07; bell notice rows #3233 07-07; Chat/Preview switch #3994 07-17; "Sent as" labels #4784 07-28; ThreadEventCard #6537 08-29; idle-gap segmentation #6534 08-29; channel stamp on outbound #11878 09-24; application card #10048 09-20; transfer card #10575 09-21; inquiry card #15524 10-02; email card flattened #16093 10-03; staff-answered card #16780 10-05; mirror rows #19204 10-13 → native #19885 10-15; lead card #20065 10-15 (part 1 of 3); "Team hand-off" rename #20166 10-15. Brand: February "Softriver" mark (ClaraAvatar.tsx, clara-avatar.svg); Focus Lab wordmark and gradient-dot icon 2026-09-23 (commit 06fe79c15a, Logo.tsx, public/brand/icon.svg, PropFlowMarkIcon); landing R1 with the orb (Figma 2445:1173).

Critic evidence still true (pass 2, three Opus lenses). claraLive is the master gate on Clara's email replies (inbox-items.ts:73–77; clara-guest-card-first-email-send.ts:94–96). Same-day channel/source/call boundaries emit a second marker with the same date (thread-segments.ts:438–465); synthetic pills :3625, :3698, :3749 are the only date on a day with no messages. "Open this conversation on its own" lives only in the pill (:4209–4227). Call cards: one per conversation, skipped without audio (thread-call-events.ts:134–162); direction only in the voice pill. Inbound rows carry no channel stamp and a missing channel is never inferred (:2692). Day rows are pinned sticky:false; the topic tint has not painted since #3196. Tests count toward the PR size gate.

Page history. Pass 1 published 18:58–19:32 (inline SVG mockups). Pass 2 rewritten 20:00–20:27 (real production Before, PNG mockups from app styles, four decisions); rejected by Fede the same evening, see rulings. Pass 3 (this page, 2026-10-15 night): inventory of the current thread UI (36 element types, 31 badge and label rows, 6 divider kinds, 9 style families, 44 blurred captures), brand sources re-read, Apple Messages measured, agent-first companies researched, an Opus 5.5 designer on max thinking paired with the lane owner over three rounds, four whole designs rendered from the app's components. Pass-2 figures d1a–d4c and after.png were deleted from the assets folder.

Research files, copied to ~/agents/006/thread-ui-standard/research/ (raw folders excluded; the harness source under pass3/harness-src): ui-inventory/inventory.md (+44 blurred PNGs; the raw folders with real names are never published), ui-design/reference/measurements.md, ui-design/competitors/competitors.md and SOURCES.md, ui-design/competitors/moodboard/moodboard.md, pass3/designer-brief.md, pass3/rules.md, the harness under pass3/tools/, the critic lenses ui-critics/a-pm-lens.md, b-brand-lens.md, c-engineering-lens.md. Brand Figma: Focus Lab "PropFlow — Retainer" (file key 8NxNMzA1VtOeCaWidYJr2u; Keychain figma-token).

Open items a session may pick up without a decision: none of the chain; it waits on the pick. Bugs already owned elsewhere: duplicate staff-answer card (team-answer-knowledge.ts), staff notices opening a channel divider (thread-segments.ts). Unverified: the Clara Insights no-op was traced in code, not clicked; Apple's current dark-mode values (the measured ones are from a 2019 press image); the moodboard shows marketing captures, not logged-in product states. Names inside the borrowed crops are the vendors' own demo content; the two crops that named real people (Raycast's founders, an Attio timeline) are blurred.

Reference: earlier passes

How the pass-4 mockups were checked

Every mockup is the app's own CSS tokens, font, Phosphor icons, the landing Orb and the real call player, rendered headless at 390 and 1440 wide, light and dark, 1x and 2x, with every edge measured in the browser. Rule: left edges, gutters and cards on the 8px grid; text rows on a 4px baseline; on a 390 phone right edges sit on the 16px gutter.

Final shots (this pass): 5,608 edge checks, 0 off the grid; 1,740 inner checks, 0 misaligned. Inner checks cover the icon inset, the label/quote/player column, the sender line flush with the bubble's right edge, and the orb centred on the sender line. Action tiles: 335 edge checks, 2 flagged, both the same quiet 20px "Incoming call" line in the two-weights tile, which sits on the text baseline by design. The 12 inner flags on the tiles were the checker, not the layout: it measured centred lines and right-aligned cards from the left edge of the row.

Found and fixed this round: the PDF chip sat 2px off the baseline (6px above it, 12px bubble padding below it, now on the grid); the sender line had been checked against the bubble's left edge (now its right edge in A2).

The thread with the 8px grid
Thread
Emails with the 8px grid
Emails
Action cards with the 8px grid
Cards

Orb 20 (pass 4 shot it at 24, today's app size)

The thread with a 20px orb
Fede picked 16: "16 is just a little cleaner".

One-on-one in the decided design: phone, light.

Pass 4, first version (2026-10-16 morning)

What changed from pass 3

Fede picks

Try it: a scrollable copy runs on localhost (see "Play with it"). The other pass-3 directions are at the bottom, for reference.

Three variants of Messages, same thread

A group thread: Maya (renter), Clara, Kat and Jay. Each variant changes only two things: where "who said it" sits, and how a call looks. Phone, light, drawn from the app's own components.

Variant A, phone, light
A · Today's spot · recommended. Orb or initial, name and channel in one small line under the last bubble, where the app has it now. The call is today's card with today's player.
Variant B, phone, light
B · Beside the bubble. iMessage's group pattern: the orb or initial next to the last bubble, no name. The call is an attachment on Maya's side.
Variant C, phone, light
C · Names only. No marks in the thread. Clara is the default "me"; only a teammate's run gets a name. The call is an action card with a play button.

Desktop and dark

Variant A, desktop, light
A · desktop
Variant A, phone, dark
A · dark
Variant B, phone, dark
B · dark
Variant C, phone, dark
C · dark

Every file at 1x and 2x: p4-variant-{A,B,C}-{390-light,390-dark,1440-light}-{1x,2x}.png in this page's asset folder. B and C desktop: B, C.

One-on-one is the same system

One-on-one thread, variant A
Only Maya and Clara: the same bubbles, line and card, nothing removed.
Actions: what happens in a thread

Today: 34 kinds of event row, drawn 12 different ways (counted in the thread code). Five categories cover all of them.

The five categories with glyphs and examples

The base: the card from propflowai.co

Marketing action cards beside the same cards at thread scale
Left: the homepage hero's action cards, rendered from the site's own component (HeroThread.tsx). Right: the same six at thread scale: tour booked, reminder sent, application sent, escalated to staff, staff replied, call summary. Same icons (Phosphor), same "label · detail".

Same or different? Three answers

Option 1: same card for every event
One card for all · recommended. The icon says what kind. Only "needs someone now" changes: amber, with a button.
Option 2: two weights
Two weights. Commitments and "needs someone" are cards; hand-offs and calls are a quiet line. Calmer, but a hand-off is easy to miss.
Option 3: a look per category
A look per category. Commitments on our side, hand-offs centred with the teammate, calls on the renter's side. Most meaning, most to learn.

Recommendation: one card for all. The question a reader asks is "what happened, and do I need to do anything?". The icon answers the first; amber answers the second. Five looks would make people learn a legend.

Labels and the green tag: what goes

Counted in the thread code. 93 message kinds; 76 of them print a label chip inside the bubble.

TodayShows onPass 4
Green "Delivered" dot (word on hover), opens "what Clara did"Only Clara's live replies, the ones with a trace. Templated, scheduled and voice sends never get it. That is why some texts are green and others aren't.Gone from the thread
Label chip in capitals: "CLARA REPLY", "FOLLOW-UP SENT", "TEXT AFTER EMAIL", "CHECKING BACK IN", and 72 moreEvery templated or scheduled sendGone. If it mattered, it is an action card
"Email" / "Text" / "Call", 10px under the bubbleEvery message we sendKept, unchanged
"Sent 9:14 AM" under emailsEmails with a send timeGone; the day line carries the time
Play with it

A scrollable copy with every control: variant A / B / C, light / dark, phone / desktop, group or one-on-one, on a 44-message thread with every event. It runs from a scratch copy of the app on Fede's Mac, never on a server. Link state is in the address, for example /thread-lab/play?v=B&theme=dark&w=phone&t=group.

How it was checked

Every mockup is the app's own CSS tokens, font (Suisse Intl), icons (Phosphor), the landing Orb and the real call player, rendered in a headless browser at 390 and 1440 wide, light and dark, 1x and 2x. An 8px grid was laid over each shot and every edge measured in the browser, not by eye.

The rule checked: left edges, gutters and card edges on the 8px grid; card heights on 8px; text rows on a 4px baseline (the body is 15/20, which no 8px grid can hold). On a 390 phone the column is 358 wide (16px gutters), so right edges sit on the gutter rather than the grid; every one is reported.

Result, final shots: 2,304 edge checks, 4 flagged; 488 inner checks (icon inset, label, quote and player on one text column, name line under its bubble), 0 off. The 4 flags are all one row: a quiet 20px text line in the "two weights" tile, which sits on the 4px text baseline by design.

MockupGrid check foundFixed
All three variantsHeader 84px tall put every day line 4px offHeader 88px
All three variantsCard text sat 10px in, off the baseline8px padding, 24px row: cards 40px
All three, call and "Kat answered" cardsThe quote and the player started 28px left of the label (a reset ate the indent)Quote, player and label share one column
A and C, desktopKat's name line hung under her wider first bubble (x 296 vs 463)The line sits under the last bubble
B"Email" / "Text" floated 32px from the run, not under the bubbleUnder the bubble; orb bottom-aligned to it
B, missed call"Clara texted b…" cut offCall lines wrap, never cut
All"2:00" and "PM" broke onto two linesTimes never break
Actions8px gap plus a dot between label and detail; the site uses one spaceOne space, like the site
Marketing vs threadRows 58px tall; the site's phone card is 30px56px rows; a 32px slot per site card
A look per category"Sat, Oct 17, 11…" cut offWraps

The grid overlays

Variant A with 8px grid
A
Variant B with 8px grid
B
Variant C with 8px grid
C
Actions, one card for all, with 8px grid
Actions

Also: one-on-one, marketing vs thread.

Against real iMessage

Apple Messages group chat beside variants A and B
Left: Apple's own group-chat screenshot (Apple Support, "Send a group text message on your iPhone", iOS 26). Others grey on the left, me blue on the right with no name, the avatar beside the last bubble of a run, the tail only on that bubble. We keep all of it except names on top, which Fede ruled out.

Pass 3 directions

Reference: other directions considered (pass 3)

Pass 3, 2026-10-15 night. Fede picked the Messages direction on 2026-10-16; the rest stays here for reference.

Before

A test conversation as it renders in production today
Production today: five date pills for two days, purple bell cards, a label chip on Clara's bubble, the February mark on her avatar, times in a code font.

Four designs, same conversation

Messages design, desktop, light
Messages · from Apple Messages. Bubbles for everyone, our side blue on the right, time only at gaps. Recommended: it is the thing Fede asked for, and it needs the fewest rules.
Linear design, desktop, light
Linear · from Linear. Flat rows, hairlines, name and time on every row. Reads like a record; loses the back-and-forth feel.
Sierra design, desktop, light
Sierra · from Sierra and Intercom Fin. The customer in a bubble, the office as plain text under a name. Long replies read best; short ones float.
Superhuman design, desktop, light
Superhuman · from Superhuman. Dense, monochrome, a violet bar on Clara's rows, channel word on every row. Most information per screen; least like a conversation.
Messages design on a phone
Messages
Linear design on a phone
Linear
Sierra design on a phone
Sierra
Superhuman design on a phone
Superhuman

Recommendation: Messages. It is iMessage drawn with our tokens and Clara's orb, so nothing on it needs explaining. The other three are real alternatives, rendered the same way, if the bubbles feel too consumer.

Fede picks

  • Design: Messages (recommended) · Linear · Sierra · Superhuman
  • Our side's fill: blue (recommended) · ink · violet
  • "Tour booked" moments: a centered line (recommended) · a small card
  • Clara's face: the orb (recommended) · the mono mark
  • Name row: when the sender changes (recommended) · on every message

Everything else on this page is decided and written as a rule below, each with the app it was borrowed from.

Every render: phone and desktop, light and dark

Drawn from the app's real components in a headless browser, 390 and 1440 wide, light and dark, on one synthetic thread (invented people, a 15-message leasing conversation over three days: website inquiry, a tour, a teammate's reply, a day of silence, a follow-up).

Messages

messages, 1440, light
1440 · light
messages, 1440, dark
1440 · dark
messages, 390, light
390 · light
messages, 390, dark
390 · dark

Linear

linear, 1440, light
1440 · light
linear, 1440, dark
1440 · dark
linear, 390, light
390 · light
linear, 390, dark
390 · dark

Sierra

sierra, 1440, light
1440 · light
sierra, 1440, dark
1440 · dark
sierra, 390, light
390 · light
sierra, 390, dark
390 · dark

Superhuman

superhuman, 1440, light
1440 · light
superhuman, 1440, dark
1440 · dark
superhuman, 390, light
390 · light
superhuman, 390, dark
390 · dark
The brand we drew from

Captured from the live site and from the app's current components on main, 2026-10-15. Clara is the landing page's orb; the wordmark is the Focus Lab "Propflow".

The landing page hero thread Brand components rendered from main, light Brand components rendered from main, dark

The strip shows the four marks this page draws from: the wordmark, the brand mark, the mono mark and the landing orb. Not in the strip, on purpose: ClaraAvatar.tsx and the chat chip, which the app still ships today, both draw the February mark (six dots and two bars in a filled shape), the one Fede called the old logo; on this page it appears only inside the captures of production as it is today (the before figure and the inventory), never in any design. Neither appears in any design here. Clara's face in the thread is the landing orb, at 20px, the same component the site uses. A follow-up PR points both old components at it.

One line of the design standards disagrees with the live brand: §7.1.8 forbids "blurry gradient balls". It was written in August, before Focus Lab's September work; the landing page has drawn Clara as the orb since. The rule gets one sentence changed: the orb is Clara's face, at 20–48px, never as decoration.

What we borrowed

Inspiration first, invention last. Each crop is from the company's own public page, cut to the part we borrow.

Apple Messages, iPhone
Apple Messages, iPhone · The whole grammar: gray received left, blue sent right, bubbles hug the text, time only at gaps, no names in a 1:1 thread. source
Apple Messages, Mac
Apple Messages, Mac · The desktop version of the same thread: wider column, same bubbles, same gaps, nothing added. source
Apple Messages, dark
Apple Messages, dark · Black ground, blue sent, #26252A received, one small "Delivered" line. Apple's 2019 press image; the only dark thread on its pages. source
Linear
Linear · A centered small time line ("Today 17:47") and one quiet gray status line; the person's request is a small bubble, the reply is plain text. source
ChatGPT
ChatGPT · The person's bubble hugs its text with no avatar or name; a whole run of work collapses into one gray line above the answer. source
Notion AI
Notion AI · What was done is one gray row per thing, verb gray and object dark, with a chevron for detail. Our event lines. source
Cursor
Cursor · Two-tone gray lines between the prose for each step; a result is one inline card. source
Vercel v0
Vercel v0 · A glyph plus a short gray phrase per step, so a long run scans like a log. Our glyph-plus-phrase event line. source
Perplexity
Perplexity · "Researched 32s": a muted word plus a value, no card. The pattern for "Tour booked · Sat 11:00 AM". source
Perplexity, files
Perplexity, files · An attachment is one outlined card, bold title, type under it, the action at the right. Our call card. source
Cursor, tool call
Cursor, tool call · A collapsed row that opens to the detail on demand. Our "Transcript" under a call. source
Raycast
Raycast · Identity once, as a quiet subtitle in the header, so no message needs a label. Our thread header: name, property, unit. source
Dia
Dia · Brand color on the person's pill and sources as a small tile grid under one gray header. The lead card's facts. source

Apple Messages, measured

From Apple's own support images (sizes as ratios; the point values are the usual iOS numbers and are marked unverified).

PropertyMeasurediOS points (unverified)
Corner radiusThe ends are fully round: radius equals half of a one-line bubble17–18
Gap, same senderAbout 1/9 of a one-line bubble3–4
Gap after a sender changeThe same; air appears only at time breaks4–8
Bubble max width73% of the screen75%
PaddingAbout 0.3× the type size each side12 h / 7 v at 17pt
Received fill#E9E9EBsystemGray5
Sent fill#43B4FA mid-bubble (a vertical gradient)#0A84FF / #007AFF
TimeCentered, small, gray, only at gaps ("Yesterday 6:05 PM")11pt secondary
Delivery"Delivered" / "Read" under the last sent bubble only11pt secondary
Sender nameNone in a 1:1 thread; group threads name received bubbles only—
TailiPhone sent bubbles have one; Mac bubbles are tail-less—
DarkFrom Apple's iOS 13 press image (2019): background #000000, received #26252A, sent #1687FF to #258DFF (a gradient). No current-era dark thread exists on Apple's pages.#26252A / #0A84FF, unverified for iOS 26

Sources: support.apple.com/guide/iphone/send-and-reply-to-messages-iph82fb73ba3/ios · apple.com/newsroom/2019/06/apple-previews-ios-13 (dark) · cdsassets.apple.com/live/7WUAS350/images/ios/ios-26-iphone-17-pro-messages-conversation-text-formatting.png · support.apple.com/guide/messages/welcome/mac. Measured with connected-region boxes of the bubble color; method in the agents fold.

What others do

The agent-first companies Fede named, from their own public pages (help centers, marketing shots, video frames; none behind a login). Full notes with every image URL are in the research files listed under "For agents".

ProductWhat they do in a threadWe take / we leave
SierraNo bubbles in the customer-facing demo: each turn carries a small logo and the business name above plain text; the person's turn fades above it. Builder chat: the person in a light box, the agent as plain text.Take: the name-above-plain-text turn (our Sierra design). Leave: no bubbles at all.
Intercom FinFin's answer is a blue block with an "Answer" label and a black "AI" badge; a source link sits inside it; an SMS system note is a yellow block; the channel is a small footer icon.Leave: boxing and badging the AI, colored system blocks. Take: the channel as a footer-sized detail, not a label.
HarveyPerson right in a gray box, Harvey as plain text; the agent, model, sources and outputs live in a side panel, never on the turns.Take: provenance away from the messages (our details panel).
GranolaPerson as a gray pill at the right; the AI as plain text; what it did as muted one-line steps with a small icon above the answer.Take: the muted one-line step (our event line).
AttioAsk Attio: person pill right, AI plain. Record timeline: an outside system is a named actor with its logo ("Linear changed 4 attributes"), relative time at the right.Take: an outside system named once, as a fact, not as a style. Leave: logos on rows.
ClayPerson as a light-gray pill; the AI as plain text with headings; one muted status line ("Workspace memory updated").Take: one status line, no card.
DecagonIllustrations only: a brand-colored bubble for the person, white for the bot, a lavender block for the analyst chat; email, chat and actions on separate surfaces.Leave: brand color on both sides, separate surfaces per channel.
SuperhumanDense monochrome rows, an AI summary card above the thread, drafted replies in a lighter box; marketing illustrations.Take: density and monochrome (our Superhuman design). Leave: the summary card.
WhatsAppBusiness threads: green sent right, white received left, time and ticks inside the bubble, a voice call as a card in the same thread, action chips under a bubble, a centered disclosure line.Take: the call as a card in the flow. Leave: time inside every bubble.
Apple MessagesMeasured in "What we borrowed". Apple's own pages show no dark-mode thread; the dark values on this page are the known system colors, unverified.Take: everything measurable.

What we are not: a property-management inbox that repeats avatar, name and time on every message, gives each system event its own colored banner, and labels where a row came from on the row. That is the Before.

Sierra, customer-facing turn
Sierra, customer-facing turn · Logo and business name above plain text; no bubble. Video frame. source
Intercom Fin
Intercom Fin · The AI answer as a blue block with an "Answer" label and badge. What we leave. source
Intercom, SMS note
Intercom, SMS note · A system note as a yellow block. What we leave. source
Harvey
Harvey · Agent, model and sources in a side panel, never on the turn. Our details panel. source
Granola
Granola · Muted one-line steps above the answer. Our event line. source
Attio, activity
Attio, activity · An outside system as a named actor, once. Our "Sent through AppFolio". source
WhatsApp business
WhatsApp business · A voice call as a card inside the thread. Our call card. source
The rules, each with its source

These hold for every design above. They are decided; the page asks nothing about them.

#RuleValueBorrowed from
1One bubble for everyone who writesProspect, Clara, teammate, a row copied from AppFolio: the same shape, the same type, the same gaps.Apple Messages; Fede
2SidesWhat came in sits on the left. What we sent (Clara and teammates alike) sits on the right.Apple Messages sent/received
3Received fill--color-bubble-incoming #F2F2F7 light / #3A3A3C dark, text --color-bubble-incoming-text (#000000 light, #F4F4F6 dark).Apple systemGray5; app token
4Our side's fillBlue #007AFF light / #0A84FF dark, white text (the one open choice below).Apple Messages; app token since 2026-08-21
5Bubble geometryRadius 16, padding 10×16, a tail on the last bubble of a run only, max width 75% at 390 and 456px (about 60%) at 1440. No border.Apple (ends fully round, 73% width); landing padding
6Rhythm4px between bubbles from the same sender (within five minutes), 8px after a sender change, 16px around a time line, 12px around an event line. Nothing between bubbles but whitespace.Apple (measured 1/9 of a bubble; air only at time breaks)
7NamesThe customer is never named in the thread (the header names them). Our side shows a 12px muted name row only when the sender changes, 4px above the bubble: Clara with her orb, a teammate with a 20px initials disc (8px initials at 500 on the gray well).Apple group threads; Raycast (identity once, in the header)
8Clara's faceThe landing page orb at 20px. Never the February mark, never a face, never a sparkle.propflowai.co hero thread
9TimeOne centered 12px muted line at gaps: at a new day "Tuesday, Oct 13 · 9:11 AM" (the day at 500), after more than an hour of same-day silence the time alone ("4:40 PM"). The product says Today and Yesterday like iMessage. No time on bubbles.Apple; Linear ("Today 17:47")
10EventsOne 12px muted line: a 16px Phosphor glyph, the verb at 500, the object at 400. "Tour booked · Sat, Oct 17, 11:00 AM", "Asked the leasing team · pet deposit". Centered in Messages and Sierra, left in the row designs. It never looks like the time line (which has no glyph and sets the day at 500).Notion AI rows; v0 glyph+phrase; Cursor two-tone; Perplexity "Researched 32s"
11CardsOnly for something with content: the lead (source and home), a call (a 28px play button, a one-line gist, "Transcript" folded), an attachment. Radius 16, card fill, a 1px --color-border hairline, no shadow, no tint, full width at 390.Perplexity files card; ChatGPT edits card; Cursor collapsed tool row
12Email in a bubbleThe subject in 500 weight above the body; the body as a text. Nothing else marks an email.Ours; no reference draws email as chat
13No channel wordA text has none, an email has its subject, a call is a card. The channel is in details.Apple (no metadata on bubbles)
14Rows copied from a property systemDrawn by the same code as a row sent through our line; the renderer never reads where a row came from. "Sent through AppFolio" and "Open in AppFolio" appear only in the details panel, which opens under the bubble (sent by, channel, sent, sent through, delivery, open in). Yardi is the same panel with another name, from the adapter.Fede 2026-10-14; Raycast (the system named once, away from the messages)
15DeliveryNothing when it worked. A failure prints one red line under the bubble, every channel, every source.Apple ("Delivered" under the last bubble only; "Not Delivered" in red)
16TypeSuisse Intl, 14.7/22 body at 400 with -0.005em tracking, 12px meta (names, times, events), 500 for the one emphasised word. No bold, no mono. Numbers never break from their unit ("2:00 PM").Landing .r1-bubble values
17ColorApp tokens only. Violet lives on Clara's orb and nowhere else. No badges, pills, uppercase labels, borders or icons on a message; no green dots.Design standards §18; Fede ("a few clean elements")
18IconsPhosphor, regular, 16px, muted.Design standards §7
19HeaderName at 14.7/500 over "property · unit" in 12px muted, centered. No "last activity", no flag, no preview switch.Apple Messages on Mac; Raycast subtitle
20ActionsThumbs and copy as ghost icons on hover, one place, never printed.ChatGPT, Notion AI, Raycast
21DarkThe same page with the tokens flipped: bg #0A0A0F, received #3A3A3C, text #F4F4F6, sent #0A84FF (iOS dark blue; no app token yet), cards on #14141D.App tokens; Apple dark values measured from a 2019 press image; current ones unverified

What each of the other three designs changes: Linear drops rules 2, 5, 6 and 9 for flat rows with a name and time on every row and a hairline between them. Sierra keeps the customer's bubble and renders our side as a name row plus plain text, no bubble. Superhuman drops the bubbles entirely, prints the channel word on every row, and marks Clara's rows with a violet bar.

The few choices, each rendered

Our side's fill

Blue fill
ABlue. iMessage's sent color, the app's outgoing blue since August. Recommended: it says "we sent this" without a label.
Ink fill
BInk. The landing page's dark bubble. Most on-brand, heaviest on a long thread.
Violet fill
CViolet. Clara's color on every sent bubble, teammates included. Breaks "purple is Clara's mark and nothing else".

"Tour booked" moments

Event as a centered line
AA centered line. Like "Delivered" in Messages. Recommended.
Event as a small card
BA small card. Easier to tap, more furniture.

Clara's face

Clara as the orb
AThe orb. The landing page's Clara. Recommended.
Clara as the mono mark
BThe mono mark. The Focus Lab icon in one color. Quieter, reads as the company, not as her.

Name row

Name only when the sender changes
AWhen the sender changes. Messages' group-thread rule. Recommended.
Name on every message
BOn every message. Never ambiguous, twice the metadata.
Proof: a row copied from AppFolio, and the details panel

Sam answered twice by text: once through our line, once typed in AppFolio and copied over. The two rows are the same bubble; the only place the system is named is the details panel.

Two teammate rows, one through our line and one copied from AppFolio, identical
Two rows from Sam. One came through our line, one was copied from AppFolio. Nothing tells them apart.
The details panel open under a copied row
Details, opened under the copied row: sent by, channel, time, sent through AppFolio, delivery, open in AppFolio. Plain words, sentence case.

The same panel serves a Yardi row tomorrow: "Sent through Yardi", "Open in Yardi". The provider name is the only thing that changes, and it comes from the adapter, never from thread code.

What goes from the page around the thread
  • The "gave a specific time" flag in the header. Grading stays on the review page.
  • "Last activity 6 hours ago." The last message's time already says it.
  • The "View preview" / "View chat" switch and the three-dots "View preview" item, once each email has "Open original" in its details.
  • The "Clara insights" bulb on pages where it opens nothing.
  • "Onboarding & Go Live" in the sidebar. It moves into Settings.
PropFlow Docs