From 0c8eaaf74bbac02c022616aff74b730d9b05c0fc Mon Sep 17 00:00:00 2001 From: maaz519 Date: Sat, 25 Jul 2026 17:01:55 +0530 Subject: [PATCH 1/6] fix(messaging-ui): unreadable code chip on own messages + raw markers in previews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs from the formatting work, both visible in the CRM messenger: 1. Inline code / code blocks were invisible in your OWN bubbles. `.miu-code` sets background: --miu-panel-2 (#1d1d26) while the is-mine override set only the colour to --miu-accent-text (#1a1206) — near-black on near-black, ~1.03:1 contrast. The chip now tints the accent bubble (rgba(0,0,0,.16)) instead of using the panel colour, so it reads against any accent. 2. The conversation list showed the raw last message, so a strikethrough message previewed as "~crazy~". Adds stripMarkup() — markers off, code unwrapped, nesting handled, honouring the same word-boundary rule so snake_case_name and "5 * 3" survive — and uses it for the preview line. Bumped to 0.1.10. SDK suite: 17 files / 98 tests green. Co-Authored-By: Claude Opus 4.8 --- packages/iios-messaging-ui/package.json | 2 +- .../src/components/conversation-list.tsx | 3 ++- packages/iios-messaging-ui/src/index.ts | 2 +- .../iios-messaging-ui/src/rich-text.test.tsx | 20 ++++++++++++++++++- packages/iios-messaging-ui/src/rich-text.tsx | 19 ++++++++++++++++++ packages/iios-messaging-ui/src/styles.css | 10 ++++++++-- 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/iios-messaging-ui/package.json b/packages/iios-messaging-ui/package.json index 78d31d1..9f968ac 100644 --- a/packages/iios-messaging-ui/package.json +++ b/packages/iios-messaging-ui/package.json @@ -1,6 +1,6 @@ { "name": "@insignia/iios-messaging-ui", - "version": "0.1.9", + "version": "0.1.10", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/iios-messaging-ui/src/components/conversation-list.tsx b/packages/iios-messaging-ui/src/components/conversation-list.tsx index 828a910..f4ba1c3 100644 --- a/packages/iios-messaging-ui/src/components/conversation-list.tsx +++ b/packages/iios-messaging-ui/src/components/conversation-list.tsx @@ -1,4 +1,5 @@ import type { Conversation } from '../types'; +import { stripMarkup } from '../rich-text'; /** Initials for the avatar chip — first letters of the first two words. */ function initials(title: string): string { @@ -37,7 +38,7 @@ export function ConversationList({ {c.title} - {c.lastMessage ? {c.lastMessage} : null} + {c.lastMessage ? {stripMarkup(c.lastMessage)} : null} {c.unread > 0 ? ( diff --git a/packages/iios-messaging-ui/src/index.ts b/packages/iios-messaging-ui/src/index.ts index 466ef9e..1275866 100644 --- a/packages/iios-messaging-ui/src/index.ts +++ b/packages/iios-messaging-ui/src/index.ts @@ -10,7 +10,7 @@ export { useChannels } from './hooks/use-channels'; export { useMembers } from './hooks/use-members'; export { isOwnMessage } from './types'; // Message-body markup (bold/italic/strike/code/links + mentions) as a safe ReactNode tree. -export { renderRichText, safeHref } from './rich-text'; +export { renderRichText, safeHref, stripMarkup } from './rich-text'; // Rendered UI. Pair with the './styles.css' export (or override the --miu-* tokens). export { Messenger } from './components/messenger'; diff --git a/packages/iios-messaging-ui/src/rich-text.test.tsx b/packages/iios-messaging-ui/src/rich-text.test.tsx index 46d3802..6fb6691 100644 --- a/packages/iios-messaging-ui/src/rich-text.test.tsx +++ b/packages/iios-messaging-ui/src/rich-text.test.tsx @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; -import { renderRichText, safeHref } from './rich-text'; +import { renderRichText, safeHref, stripMarkup } from './rich-text'; function show(text: string, names: string[] = []) { render(
{renderRichText(text, names)}
); @@ -75,3 +75,21 @@ describe('renderRichText', () => { expect(show('just a normal message').textContent).toBe('just a normal message'); }); }); + +describe('stripMarkup (one-line previews)', () => { + it('drops the markers so a preview never shows ~crazy~', () => { + expect(stripMarkup('~crazy~')).toBe('crazy'); + expect(stripMarkup('*bold* and _italic_')).toBe('bold and italic'); + }); + + it('unwraps code and flattens a fenced block', () => { + expect(stripMarkup('run `npm ci` now')).toBe('run npm ci now'); + expect(stripMarkup('```\nconst a = 1;\n```')).toBe('const a = 1;'); + }); + + it('handles nesting and leaves ordinary text (and identifiers) alone', () => { + expect(stripMarkup('*bold with _italic_*')).toBe('bold with italic'); + expect(stripMarkup('snake_case_name and 5 * 3')).toBe('snake_case_name and 5 * 3'); + expect(stripMarkup('plain preview')).toBe('plain preview'); + }); +}); diff --git a/packages/iios-messaging-ui/src/rich-text.tsx b/packages/iios-messaging-ui/src/rich-text.tsx index f0a94bd..b760f21 100644 --- a/packages/iios-messaging-ui/src/rich-text.tsx +++ b/packages/iios-messaging-ui/src/rich-text.tsx @@ -71,6 +71,25 @@ function firstSpan(text: string): { start: number; end: number; rule: Rule; inne return best; } +/** + * The same text with its formatting markers removed, for places that show a one-line plain-text + * preview (conversation list, notifications) where `~crazy~` must read as "crazy" — you cannot + * render nodes into those, so the markers have to come off rather than be styled. + */ +export function stripMarkup(text: string): string { + const noCode = text + .replace(/```([\s\S]*?)```/g, (_m, code: string) => code.trim()) + .replace(/`([^`\n]+)`/g, '$1'); + return stripSpans(noCode); +} + +/** Recursively drop *bold* / _italic_ / ~strike~ markers, honouring the same word-boundary rule. */ +function stripSpans(text: string): string { + const span = firstSpan(text); + if (!span) return text; + return text.slice(0, span.start) + stripSpans(span.inner) + stripSpans(text.slice(span.end)); +} + /** Linkify + mention-highlight a run of text that carries no other markup. */ function plain(text: string, memberNames: string[], keyed: () => string): ReactNode[] { const out: ReactNode[] = []; diff --git a/packages/iios-messaging-ui/src/styles.css b/packages/iios-messaging-ui/src/styles.css index 2c8922e..0340cf0 100644 --- a/packages/iios-messaging-ui/src/styles.css +++ b/packages/iios-messaging-ui/src/styles.css @@ -568,8 +568,14 @@ button.miu-attach-dl:hover { color: var(--miu-accent); text-decoration: underline; } -.miu-msg.is-mine .miu-bubble .miu-link, -.miu-msg.is-mine .miu-bubble .miu-code { +.miu-msg.is-mine .miu-bubble .miu-link { + color: var(--miu-accent-text); +} +/* On your own (accent-filled) bubble the panel background would sit dark-on-dark against the dark + accent text — tint the bubble instead so the chip reads on any accent colour. */ +.miu-msg.is-mine .miu-bubble .miu-code, +.miu-msg.is-mine .miu-bubble .miu-code-block { + background: rgba(0, 0, 0, 0.16); color: var(--miu-accent-text); } .miu-send { From 9a0c74cb6e834de941253db7c9decee12e85dd0a Mon Sep 17 00:00:00 2001 From: maaz519 Date: Sat, 25 Jul 2026 17:16:28 +0530 Subject: [PATCH 2/6] fix(messaging-ui): composer no longer stretches the send/attach buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The textarea swap made the composer row tall and dragged the controls with it. Three causes, all fixed: - No box-sizing anywhere in the stylesheet, so `min-height: 38px` on a padded, bordered textarea rendered ~58px (content-box adds 18px padding + 2px border on top). The textarea is now border-box with a 40px min-height — the same height the old single-line input had. - .miu-composer-row is display:flex with no align-items, so it defaulted to `stretch` and the buttons — neither of which declared a height — grew to the row. Now align-items: flex-end, so controls stay pinned to the bottom while the box grows upward (WhatsApp behaviour), with an explicit 40px on both. The send height is scoped to .miu-composer so modal/settings buttons keep their own sizing. - The auto-grow effect set height = scrollHeight, which under content-box double-counted padding on every keystroke. It now compensates for the border explicitly, correct under border-box. Bumped to 0.1.11. SDK suite: 17 files / 98 tests green. Co-Authored-By: Claude Opus 4.8 --- packages/iios-messaging-ui/package.json | 2 +- .../src/components/composer.tsx | 5 ++++- packages/iios-messaging-ui/src/styles.css | 18 +++++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/iios-messaging-ui/package.json b/packages/iios-messaging-ui/package.json index 9f968ac..d250721 100644 --- a/packages/iios-messaging-ui/package.json +++ b/packages/iios-messaging-ui/package.json @@ -1,6 +1,6 @@ { "name": "@insignia/iios-messaging-ui", - "version": "0.1.10", + "version": "0.1.11", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/iios-messaging-ui/src/components/composer.tsx b/packages/iios-messaging-ui/src/components/composer.tsx index 20ecc33..e0beb6f 100644 --- a/packages/iios-messaging-ui/src/components/composer.tsx +++ b/packages/iios-messaging-ui/src/components/composer.tsx @@ -54,11 +54,14 @@ export function Composer({ const inputRef = useRef(null); // Grow with the content up to the CSS max-height, then scroll — a chat box, not a fixed field. + // The box is border-box, but scrollHeight excludes the border, so add it back or every measure + // lands a couple of pixels short and the textarea shows a scrollbar it doesn't need. useEffect(() => { const el = inputRef.current; if (!el) return; el.style.height = 'auto'; - el.style.height = `${el.scrollHeight}px`; + const border = el.offsetHeight - el.clientHeight; + el.style.height = `${el.scrollHeight + border}px`; }, [draft]); /** diff --git a/packages/iios-messaging-ui/src/styles.css b/packages/iios-messaging-ui/src/styles.css index 0340cf0..f6c07cf 100644 --- a/packages/iios-messaging-ui/src/styles.css +++ b/packages/iios-messaging-ui/src/styles.css @@ -371,13 +371,17 @@ .miu-composer-row { display: flex; gap: 8px; + /* Pin the controls to the bottom so a growing textarea never stretches them (WhatsApp). */ + align-items: flex-end; } .miu-file-input { display: none; } .miu-attach-btn { flex-shrink: 0; - width: 38px; + box-sizing: border-box; + width: 40px; + height: 40px; border-radius: 10px; border: 1px solid var(--miu-border); background: var(--miu-panel); @@ -515,10 +519,13 @@ button.miu-attach-dl:hover { .miu-input:focus { border-color: var(--miu-accent); } -/* Chat box: one row by default, grows with content (JS sets height), then scrolls. */ +/* Chat box: one row by default, grows with content (JS sets height), then scrolls. + border-box so min/max-height are the REAL height — under the default content-box the padding + and border are added on top, which made a "38px" box render ~58px and dragged the row with it. */ .miu-textarea { + box-sizing: border-box; resize: none; - min-height: 38px; + min-height: 40px; max-height: 160px; overflow-y: auto; line-height: 1.45; @@ -587,6 +594,11 @@ button.miu-attach-dl:hover { color: var(--miu-accent-text); background: var(--miu-accent); } +.miu-composer .miu-send { + box-sizing: border-box; + flex-shrink: 0; + height: 40px; +} .miu-send:disabled { opacity: 0.5; cursor: not-allowed; From e2f66fe622d5f8bebddd49e6a4623ce6d9c37548 Mon Sep 17 00:00:00 2001 From: maaz519 Date: Sat, 25 Jul 2026 17:29:46 +0530 Subject: [PATCH 3/6] fix(messaging-ui): composer input back to its original 38px height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.1.11 left it at 40px with line-height 1.45, so the line (20.3px) overflowed the 20px content box — taller than the it replaced, and liable to show a scrollbar on a single line. Collapsed height is now one token, --miu-composer-h: 38px, shared by the textarea, attach and send so they cannot drift apart again. Padding tightened to 8px (from .miu-input's 9px) with line-height 1.4, so a 14px line is 19.6 + 16 + 2 = 37.6px and fits exactly — matching the original single-line input. Co-Authored-By: Claude Opus 4.8 --- packages/iios-messaging-ui/package.json | 2 +- packages/iios-messaging-ui/src/styles.css | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/iios-messaging-ui/package.json b/packages/iios-messaging-ui/package.json index d250721..4c0fb1d 100644 --- a/packages/iios-messaging-ui/package.json +++ b/packages/iios-messaging-ui/package.json @@ -1,6 +1,6 @@ { "name": "@insignia/iios-messaging-ui", - "version": "0.1.11", + "version": "0.1.12", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/iios-messaging-ui/src/styles.css b/packages/iios-messaging-ui/src/styles.css index f6c07cf..5cdd7b3 100644 --- a/packages/iios-messaging-ui/src/styles.css +++ b/packages/iios-messaging-ui/src/styles.css @@ -10,6 +10,8 @@ --miu-accent: #fda913; --miu-accent-text: #1a1206; --miu-radius: 12px; + /* Collapsed composer height — textarea, attach and send all use this so they never diverge. */ + --miu-composer-h: 38px; display: flex; height: 100%; @@ -380,8 +382,8 @@ .miu-attach-btn { flex-shrink: 0; box-sizing: border-box; - width: 40px; - height: 40px; + width: var(--miu-composer-h); + height: var(--miu-composer-h); border-radius: 10px; border: 1px solid var(--miu-border); background: var(--miu-panel); @@ -525,10 +527,14 @@ button.miu-attach-dl:hover { .miu-textarea { box-sizing: border-box; resize: none; - min-height: 40px; + /* Collapsed height must equal the single-line this replaced: 20px of line + 16px + padding + 2px border = 38px. Padding is tightened from .miu-input's 9px so a 14px/1.4 line + fits the content box exactly — at 9px it overflowed and forced a scrollbar. */ + padding: 8px 12px; + line-height: 1.4; + min-height: var(--miu-composer-h); max-height: 160px; overflow-y: auto; - line-height: 1.45; font-family: inherit; } .miu-format-bar { @@ -597,7 +603,7 @@ button.miu-attach-dl:hover { .miu-composer .miu-send { box-sizing: border-box; flex-shrink: 0; - height: 40px; + height: var(--miu-composer-h); } .miu-send:disabled { opacity: 0.5; From eb0ace8ad7e03ed865a3bef53fc8aae720b42e6b Mon Sep 17 00:00:00 2001 From: maaz519 Date: Sat, 25 Jul 2026 17:35:40 +0530 Subject: [PATCH 4/6] fix(messaging-ui): composer height was being clobbered by a class-name collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chat composer reused .miu-textarea, which the INBOX MAIL composer already owned further down the stylesheet with `resize: vertical; min-height: 90px`. Equal specificity, later rule wins — so the mail styling applied to the chat box: 90px tall with a resize grabber, and every height fix in 0.1.10–0.1.12 was silently overridden. That is why the box never changed. The composer now uses its own .miu-composer-box; the inbox rule is untouched. Adds a regression test asserting the composer does not carry .miu-textarea. Co-Authored-By: Claude Opus 4.8 --- packages/iios-messaging-ui/package.json | 2 +- .../iios-messaging-ui/src/components/composer.test.tsx | 8 ++++++++ packages/iios-messaging-ui/src/components/composer.tsx | 2 +- packages/iios-messaging-ui/src/styles.css | 4 +++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/iios-messaging-ui/package.json b/packages/iios-messaging-ui/package.json index 4c0fb1d..c5a77d0 100644 --- a/packages/iios-messaging-ui/package.json +++ b/packages/iios-messaging-ui/package.json @@ -1,6 +1,6 @@ { "name": "@insignia/iios-messaging-ui", - "version": "0.1.12", + "version": "0.1.13", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/iios-messaging-ui/src/components/composer.test.tsx b/packages/iios-messaging-ui/src/components/composer.test.tsx index c6947f8..3896a32 100644 --- a/packages/iios-messaging-ui/src/components/composer.test.tsx +++ b/packages/iios-messaging-ui/src/components/composer.test.tsx @@ -16,6 +16,14 @@ describe(' formatting + native text services', () => { expect(box.getAttribute('autocorrect')).toBe('on'); }); + it('does NOT reuse .miu-textarea — that class belongs to the inbox mail composer', () => { + // Regression: sharing it let the mail rule (resize: vertical; min-height: 90px), which is + // declared later in the stylesheet, win at equal specificity and inflate the chat composer. + const { box } = mount(); + expect(box.classList.contains('miu-composer-box')).toBe(true); + expect(box.classList.contains('miu-textarea')).toBe(false); + }); + it('Enter sends, Shift+Enter does not (it makes a newline)', async () => { const { onSend, box } = mount(); fireEvent.change(box, { target: { value: 'hello' } }); diff --git a/packages/iios-messaging-ui/src/components/composer.tsx b/packages/iios-messaging-ui/src/components/composer.tsx index e0beb6f..c7a45c9 100644 --- a/packages/iios-messaging-ui/src/components/composer.tsx +++ b/packages/iios-messaging-ui/src/components/composer.tsx @@ -203,7 +203,7 @@ export function Composer({ ) : null}