From 8e8d948ba6366576eca9a5e242b638bea79b6fd3 Mon Sep 17 00:00:00 2001 From: Void Date: Tue, 10 Nov 2026 19:22:26 +0000 Subject: [PATCH 1/3] message: a long reply quote stays inside its column (own and received); demo gets a long quote to reply to The row around a bubble sized to its content in a right-aligned column, and the quote's nowrap text made that content 782px, so on a phone the column ran off the left edge. The row, column and bubble now carry minWidth 0 / maxWidth 100%, and ReplyContent minWidth 0 so the quote truncates. Starfire 390x844: own quote 782px at left -425 -> 332px at left 25; desktop inside the bubble. No test yet. Co-Authored-By: Claude Sonnet 5.5 --- src/app/components/message/layout/Bubble.tsx | 4 +-- .../components/message/layout/layout.css.ts | 16 ++++++++++- src/preview-demo/fixtures.ts | 10 +++++++ src/preview-demo/server.ts | 28 +++++++++++-------- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/app/components/message/layout/Bubble.tsx b/src/app/components/message/layout/Bubble.tsx index bd2056b9..6f4e94b0 100644 --- a/src/app/components/message/layout/Bubble.tsx +++ b/src/app/components/message/layout/Bubble.tsx @@ -60,13 +60,13 @@ export const BubbleLayout = as<'div', BubbleLayoutProps>( {header &&
{header}
} {hideBubble ? ( children ) : ( - + number = Date.now): DemoServer => { ROOMS.forEach((room) => { stateOf.set(room.id, buildState(room)); + const lineIds: string[] = []; room.lines.forEach((l) => { + const event = mk( + 'm.room.message', + l.from, + { + msgtype: 'm.text', + body: l.body, + ...(l.html ? { format: 'org.matrix.custom.html', formatted_body: l.html } : {}), + ...(l.pose ? { [POSE_KEY]: l.pose } : {}), + ...(l.replyTo === undefined + ? {} + : { 'm.relates_to': { 'm.in_reply_to': { event_id: lineIds[l.replyTo] } } }), + }, + now() - l.minutesAgo * 60 * 1000 + ); + lineIds.push(event.event_id); timeline.push({ version: 1, roomId: room.id, - event: mk( - 'm.room.message', - l.from, - { - msgtype: 'm.text', - body: l.body, - ...(l.html ? { format: 'org.matrix.custom.html', formatted_body: l.html } : {}), - ...(l.pose ? { [POSE_KEY]: l.pose } : {}), - }, - now() - l.minutesAgo * 60 * 1000 - ), + event, }); }); }); -- 2.47.3 From 4208b1e25c7aabde8226f2ad607ed2d76974de37 Mon Sep 17 00:00:00 2001 From: Void Date: Thu, 12 Nov 2026 09:53:04 +0000 Subject: [PATCH 2/3] Pin the bubble row's shrink-to-column declarations A test reads the emitted layout.css.ts and fails when BubbleColumn, BubbleContentRow or BubbleContent lose min-width 0 or the row loses max-width 100%, the cause of the 782px own-message quote. Co-Authored-By: Claude Sonnet 5.5 --- .../message/layout/bubbleBounds.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/app/components/message/layout/bubbleBounds.test.ts diff --git a/src/app/components/message/layout/bubbleBounds.test.ts b/src/app/components/message/layout/bubbleBounds.test.ts new file mode 100644 index 00000000..3d95a839 --- /dev/null +++ b/src/app/components/message/layout/bubbleBounds.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest'; +import zlib from 'zlib'; +import { compile, processVanillaFile } from '@vanilla-extract/integration'; + +// A long reply quote (nowrap) sized the own-message row to 782px on a 390px phone. The row, the column and the bubble +// must be able to shrink to their parent, so pin the emitted declarations (as phoneTapTargets.test.ts does). +const emitted = async () => { + const filePath = `${process.cwd()}/src/app/components/message/layout/layout.css.ts`; + const { source } = await compile({ filePath, cwd: process.cwd(), identOption: 'debug' }); + const code = await processVanillaFile({ source, filePath, identOption: 'debug' }); + const bytes = Buffer.from( + (code.match(/source=#?([A-Za-z0-9+/=]+)/) as RegExpMatchArray)[1], + 'base64' + ); + return (bytes[0] === 0x1f && bytes[1] === 0x8b ? zlib.gunzipSync(bytes) : bytes).toString(); +}; + +const declarations = (css: string, className: string): string[] => { + const rule = new RegExp(`^\\.layout_${className}__[\\w-]+ \\{\\n([^}]*)\\}`, 'm').exec(css); + expect(rule, `no .layout_${className} rule emitted`).toBeTruthy(); + return (rule as RegExpExecArray)[1].split('\n').map((l) => l.trim().replace(/;$/, '')); +}; + +describe('bubble rows shrink to their column', () => { + it.each([ + ['BubbleColumn', ['min-width: 0']], + ['BubbleContentRow', ['min-width: 0', 'max-width: 100%']], + ['BubbleContent', ['min-width: 0']], + ])('%s', async (name, expected) => { + const css = await emitted(); + expect(declarations(css, name)).toEqual(expect.arrayContaining(expected)); + }); + + it('BubbleContent keeps its 800px cap inside the column width', async () => { + const css = await emitted(); + expect(declarations(css, 'BubbleContent').join('\n')).toMatch(/max-width: min\(100%, 50rem\)/); + }); +}); -- 2.47.3 From fb03aa0ffbc82d8d2e357c55b4afd5a7f779e0de Mon Sep 17 00:00:00 2001 From: Void Date: Thu, 12 Nov 2026 09:56:01 +0000 Subject: [PATCH 3/3] bubbleBounds test also pins the Bubble.tsx wiring Co-Authored-By: Claude Sonnet 5.5 --- src/app/components/message/layout/bubbleBounds.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/app/components/message/layout/bubbleBounds.test.ts b/src/app/components/message/layout/bubbleBounds.test.ts index 3d95a839..4a9ad4da 100644 --- a/src/app/components/message/layout/bubbleBounds.test.ts +++ b/src/app/components/message/layout/bubbleBounds.test.ts @@ -1,3 +1,4 @@ +import { readFileSync } from 'fs'; import { describe, expect, it } from 'vitest'; import zlib from 'zlib'; import { compile, processVanillaFile } from '@vanilla-extract/integration'; @@ -35,4 +36,13 @@ describe('bubble rows shrink to their column', () => { const css = await emitted(); expect(declarations(css, 'BubbleContent').join('\n')).toMatch(/max-width: min\(100%, 50rem\)/); }); + + it('Bubble.tsx puts BubbleColumn on the column and BubbleContentRow on the row', () => { + const tsx = readFileSync( + `${process.cwd()}/src/app/components/message/layout/Bubble.tsx`, + 'utf8' + ); + expect(tsx).toMatch(/classNames\(css\.BubbleColumn,/); + expect(tsx).toMatch(//); + }); }); -- 2.47.3