AttachToConversation.tsxrefactor(mail): stop announcing success, and fix the qualified-date parser Toasts now only fire when something went wrong. 416 success confirmations across 151 files told the user that the thing they had just watched happen had happened — the row was already gone, the draft already sent, the text already in the clipboard. Removing them took the variables and callbacks that existed only to build those messages with them. Thirteen survive, minus the success styling: the ones carrying an Undo button, where the toast is the affordance rather than a notification. Those now use the neutral toast() so they read as "here is your undo", not congratulation. toast.info / toast.warning are untouched — those report neutral or unwanted outcomes, which is not the same thing as celebrating one. Also fixes "mid august" resolving to August 1st in every date picker. chrono has no notion of early/mid/late, so it finds the month, discards the qualifier and answers the 1st — and near a month boundary forwardDate then pushed that past date into NEXT YEAR, so scheduling a send three days out landed eleven months away. resolveQualifiedPeriod runs ahead of chrono and takes the words it cannot express: named months, relative months, weeks and years, each with early / mid / late, rolling forward only when the resolved date has actually passed. And the scheduled email in a thread now renders as an ordinary message rather than a bespoke card: same MailDisplay, a Scheduled badge in the tracking-badge slot, and Edit / Delete replacing reply / reply-all / forward — there is nothing to reply to on mail that has not gone out yet. Edit unschedules before reopening, because the send payload is a frozen snapshot and would otherwise fire the old text alongside the edited one. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 12, 2026, 10:13 PMchannel-icons.tsxfeat: inbox row/view polish — colourful logos, date split, tab selector, channel view (design: omni-channel-inbox) - Full-colour brand logos (LinkedIn/WhatsApp/Slack) + Gmail for email; rendered in the subject position (after the unread dot), not next to the name. - InboxList gets the same Today/Yesterday/Last 7 days/Older date split as MailList. - ChannelSelector restyled to look exactly like a folder tab ("All Channels v"). - ChannelThreadView reworked: mail-thread-like top bar (back · counterpart · star/snooze/done commands), real Slack message rendering (from the conversation timeline) + reply for all three channels. Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 27, 2026, 12:27 PMChannelConnectPrompt.tsxfeat: inbox — connect prompt for LinkedIn/WhatsApp when the account isn't linked Selecting the LinkedIn or WhatsApp filter with no connected account now shows a circular brand-mark connect prompt (mirroring the empty-email state) that kicks off the same Unipile hosted-auth flow as settings → connections, instead of a dead empty feed. Extracts the connect+poll flow into shared hooks so the settings cards and the inbox prompt share one implementation. Co-Authored-By: Claude Opus 4.8 (1M context) <<email>>Jul 28, 2026, 11:49 PMChannelMessageList.tsxfix(slack): grow the reply box, colour the mentions, and render the real emoji The unibox's Slack/LinkedIn surface, held next to Slack itself. The reply box never grew. A textarea does not size to its content, so `rows={1}` with a `max-h` was a one-line box that scrolled internally the moment you wrote a second sentence — the `max-h` it was written against could never be reached. `SlackMentionTextarea` now measures and writes back its height on every edit (opt-in, because the table's output-cell editor is sized in `rows` and has a resize handle the user drags). Both composers cap at ~3x their resting height, and a ResizeObserver re-measures on WIDTH changes only — reacting to the height it just wrote would loop. Mentions rendered grey on grey: `bg-primary/10 text-primary` inherits a primary that is near-black in this theme, so a ping read as nothing at all. They are now blue on a lighter wash of the same blue, and the composer's TipTap chip uses the same pair, so a mention does not change colour the moment it is sent. Reactions showed the wrong glyph, in three different ways: - The dataset stores the BARE codepoint for ~170 legacy symbols (`❤` U+2764, `✔` U+2714, `⚠` U+26A0), and a bare codepoint renders in text presentation — a thin monochrome glyph, not the coloured emoji Slack shows for the very same reaction. U+FE0F is appended per codepoint, so `🤷‍♂` gets the selector after the ♂ and nowhere else, with a range table of the BMP codepoints that are already emoji by default keeping `✅` free of a pointless invisible character. - Slack writes some names with hyphens (`star-struck`, `man-shrugging`) where the dataset uses underscores. Unresolved, those chips rendered their own raw name. - A `::skin-tone-N` suffix resolved to nothing. The modifier now goes in after the base codepoint, before any ZWJ tail, which is where Unicode wants it. The chip consults Slack's key first — it is what Slack renders from and it carries the tone — with the stored `emojiUnicode` behind it, itself normalized, since it came from the same bare dataset. `SlackBodyRenderer` had a second, weaker copy of the shortcode map for `:heart:` in message text; it is gone, and body text now resolves through the same function, so a heart in the words and a heart on the reaction row are the same heart. Two smaller things from the same comparison: the day divider's rules run into the badge instead of stopping short of it, one hairline interrupted by the date; and the reply count sits at `text-xs`, below the message text rather than level with it. Co-Authored-By: Claude Opus 5 <<email>>Aug 26, 2026, 7:33 PMChannelSelector.tsxfeat(inbox,two-factor,marketing): channel filter on query tabs, 2FA policy, host routing Three in-flight threads plus supporting docs, committed together. - inbox: the channel badge no longer goes empty on a Gmail-query tab — participatingChannels stops dropping chat channels when the tab rule is mailOnly, so an explicit single-channel badge asks the sources it names. - two-factor: policy module with its own tests, wired through session verification and auth, with the env and runtime-contract keys it needs. - marketing: host routing for the signed-out root, with a redirect test. - docs: inbox channel-filter bug writeup, daily agenda templates, open-tracking v2 notes, and the 2026-08-30 customer feedback audit. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 11:53 AMChannelThreadView.tsxfix(mail): a link in a chat thread is a link LinkedIn and WhatsApp send their words as plain text, so a pasted URL rendered as characters. Slack linkified only its own <url|label> tokens — which its composer emits and a bot, an unfurl field or pasted text do not. Bare URLs are now linkified in both, before the mrkdwn formatting pass so the parser cannot read a URL's underscores as italics. The splitter is lifted out of the calendar's LinkifiedEventText into lib/linkify. Links are painted with the action ramp, not primary: primary is near-black on light and near-white on dark, the colour of the words around them. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 18, 2026, 9:38 AM1 defectInboxList.tsxfix(inbox): key the head-change latch to the query it describes Greptile, on the previous commit. The hook instance outlives the question it is asking: switching folder, inbox or the unread lens swaps the cache entry underneath it while the ref persists. A bare boolean carried the previous query's `true` into the next one, read "no transition", and skipped a reset that query genuinely needed — leaving its later pages on cursors minted against a boundary that had since moved. Id-dedup cannot clean that up, since stale rows need not collide by id. The latch now remembers which query it was describing. Also fixes the degraded-channel notice, which claimed more than it knew. A channel that had already loaded a page keeps showing it when a later refetch fails — react-query retains the last good data, and dropping rows we did fetch would be a worse answer than holding them. So the notice cannot say the channel is missing; what is missing is anything newer than what is on screen, which is equally true of a channel that never loaded at all. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 12:51 AMInboxRow.tsxfeat(inbox): unibox keyboard parity, undo-send that puts the draft back, sharper chat context Unibox now navigates like the email list — j/k, Enter, Escape all walk whichever list is on screen via a new `activeListSource` on the thread slice, and Escape clears the full selection (focused row, bulk checkboxes, ambient conversation) instead of only the keyboard cursor. Undo send stops round-tripping through localStorage and a URL rewrite: the send records where the draft came from and undo reopens that surface, restoring the un-quoted body onto the draft row (recreating it when the send removed it). Chat context: an auto-committed primary conversation is provisional while the chat is empty and drops when you move on, the context cards follow the deal you're actually on, threads with no conversation get a link prompt, and the conversation surface shows a keyboard legend rather than restating cards already on screen. Also: agenda Trash removes the row outright, Next steps renders as one card with its booked calls beneath, and channel chats focus the composer on open.Jul 30, 2026, 11:54 PMSlackThreadPanel.tsxfix(ui): a composer that grows to half the screen, not five lines The Slack/LinkedIn/WhatsApp reply box capped at 112px, so anything past about five wrapped lines scrolled inside a box too small to reread what you had just written. Cap it at half the viewport instead — the thread above keeps the other half — and give the email reply body the same 50vh ceiling, dropping its 820px cap while keeping the 250px floor. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 2, 2026, 3:18 PM