The softphone gets an inbox, and an SMS window is one thread

Featurekamo-internal
Shipped
September 6, 2026 at 5:58 PM UTC
Author
Kamo
Commit
5503548

The SMS tool window opened on a twelve-key dialpad and then became a three-tab window -- Chat / Templates / CRM -- titled "Conversation #a3f1c8de-...". None of that is how anybody uses text messages, and two of the three tabs were not real: SmsCrmTab was DUMMY_CONTACT and DUMMY_ACTIVITY ("John Smith, Acme Corp", hardcoded, shown to every member about every customer) while a genuine reverse lookup already existed in SoftphoneCRM, and SmsTemplatesTab read a five-item constant while VOIPService's SmsTemplateController went uncalled by anything. There was also nowhere to SEE your texts: the window showed one conversation and could not reach another, so the only routes to a thread were an incoming notification popping a window at you, or the Interaction Center. The window - Two states, no tabs: a recipient picker, or a thread. Templates moved to the composer's bolt button on the real API; contact detail to the header's info pane rendering SoftphoneCRM. SmsCrmTab, SmsTemplatesTab, smsTemplateData and SmsNewConversation are deleted. - Compose is recipient-first and has NO message box. That is what makes the close-and-focus case safe: a window that closes in favour of one already open closes empty by construction, so "a tool window must never lose what a member typed" holds without any careful handling. - The contact pane swaps with `display`, never a conditional render -- that would destroy the composer every time somebody glanced at it, which is the failure the old tab bar kept producing. One conversation, one window The tool store has always deduped on props.conversationId -- un-minimize, raise, glow. It could not see an SMS window opened by PHONE NUMBER, because SmsTool resolved the id into local state and never told the store, so opening that conversation from anywhere else built a second window on top of the first. SmsTool now patches the id onto its own props (as ChatTool does for its sessionGuid) and titles the window with the contact. Picking a recipient settles three ways: create here, adopt here, or raise the window that has it and close this one. The softphone A fifth badged TEXTS tab, composed from what already existed for the Interaction Center -- useConversationRows' text bucket, ConversationRow, unreadForRow, openConversation -- plus search and a new-message button. The badge reads UnreadContext.bySurface.sms, already live over the socket, so it costs no fetch. Contact names ConversationRow admitted "there is no server-side reverse lookup from a number to a contact name, so a text row is titled by its number". New **************** mirrors mailboxDirectory's shape and indexes contacts by canonicalPhone, in the browser because the recipient search loads them anyway. One index re-titles the header, the TEXTS list and the navigator. Also fixed: SmsNotificationListener stamped every socket frame INBOUND and ignored the direction the event has always carried, so a member's own outbound text came back rendered as the customer's and raised a window at them for something they had just done. The window now gates the whole texting surface on GET /sms/capability instead of letting a Teams or FreePBX org discover it by pressing send into silence, and a refused send shows the provider's own sentence with the message marked Not delivered. Requires the voipservice commit that adds /sms/capability and the send outcome.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing