fix(web-shell): keep the user-selectable wrapper out of flex layout (#6229)

This commit is contained in:
carffuca 2026-07-03 12:29:21 +08:00 committed by GitHub
parent 8de93b876b
commit e2e94bc725
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 1 deletions

View file

@ -141,3 +141,26 @@ describe('MessageItem error isolation', () => {
expect(alert.style.justifyContent).toBe('flex-start');
});
});
describe('MessageItem selectable wrapper', () => {
it('keeps the user-selectable wrapper out of layout via display: contents', () => {
// The wrapper only exists to carry the `data-user-selectable` CSS marker
// (standalone.css re-enables text selection through it). It must NOT
// generate a layout box: several parents are flex containers whose item
// used to be the message body itself — a plain div here becomes the flex
// item instead and shrinks to content width, squeezing the user chat
// bubble (max-width: 80% of the shrunken wrapper) so even short messages
// wrap mid-word.
const container = render(
<I18nProvider language="en">{item(userMsg('1', 'hello'))}</I18nProvider>,
);
const wrapper = container.querySelector(
'[data-user-selectable]',
) as HTMLElement;
expect(wrapper).not.toBeNull();
expect(wrapper.style.display).toBe('contents');
// The message body renders inside the wrapper, so the CSS descendant
// selector `[data-user-selectable] *` still re-enables selection.
expect(wrapper.querySelector('[data-testid="user-ok"]')).not.toBeNull();
});
});

View file

@ -154,7 +154,20 @@ export const MessageItem = memo(function MessageItem({
// standalone.css disables selection on UI chrome (native-app feel); this
// attribute opts the message subtree back in, including descendants
// (Markdown body, code blocks, tool panels, sub-messages).
const selectableSafeBody = <div data-user-selectable="true">{safeBody}</div>;
//
// `display: contents` keeps this wrapper out of layout: several parents
// (e.g. MessageTimestamp's chat row) are flex containers whose items used
// to be the message body itself. A plain div here becomes the flex item
// instead and shrinks to its content width, squeezing user chat bubbles
// (whose max-width: 80% then resolves against the shrunken wrapper) so
// even short messages wrap mid-word. The user-select re-enable rule
// matches `[data-user-selectable] *`, so the boxless wrapper does not
// affect it.
const selectableSafeBody = (
<div data-user-selectable="true" style={{ display: 'contents' }}>
{safeBody}
</div>
);
if (message.role === 'assistant') {
if (showAssistantActions) {