Keep bot messages inside the column on narrow screens

Lists no longer add twui's 25px list margin on top of full width,
markdown blocks and list items are capped at the column, long words
and inline code wrap anywhere, and ref chips with long labels truncate
instead of pushing the line past the edge.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-28 06:23:09 +01:00
co-authored by Claude Opus 5.5
parent 4e20106c59
commit 3e78d51b56
15 changed files with 45 additions and 27 deletions
@@ -42,7 +42,7 @@ export const MessageMarkdownComponents: Components = {
</MarkdownList>
),
code: ({ children }) => (
<Span className="font-mono text-[13px] px-1 py-px rounded-default-xs bg-sunken text-ink break-words">
<Span className="font-mono text-[13px] px-1 py-px rounded-default-xs bg-sunken text-ink wrap-anywhere">
{children}
</Span>
),
@@ -58,7 +58,7 @@ export const MessageMarkdownComponents: Components = {
),
table: ({ children }) => (
<Stack className="w-full max-w-full overflow-x-auto items-start">
<table className="text-[13px] leading-5 text-ink border-collapse">{children}</table>
<table className="text-[13px] leading-5 text-ink border-collapse wrap-break-word">{children}</table>
</Stack>
),
th: ({ children }) => (
@@ -39,7 +39,7 @@ export const UserMarkdownComponents: Components = {
</MarkdownList>
),
code: ({ children }) => (
<Span className="font-mono text-[13px] px-1 py-px rounded-default-xs bg-on-ink/15 text-on-ink break-words">
<Span className="font-mono text-[13px] px-1 py-px rounded-default-xs bg-on-ink/15 text-on-ink wrap-anywhere">
{children}
</Span>
),
@@ -65,7 +65,7 @@ function BotMessageLine({ message, bot_name, at }: Props) {
) : null}
<Stack
{...{ [QuotableAttribute]: "" }}
className="w-full gap-2.5! items-start break-words"
className="w-full gap-2.5! items-start wrap-anywhere [&>*]:max-w-full"
>
<Markdown
remarkPlugins={RemarkPlugins}
@@ -35,7 +35,7 @@ export default function BotRefChip({ bot_id }: Props) {
: `/admin/bots/${bot.id}`
}
title={`Open ${bot.name}`}
className="inline-flex h-6 gap-1.5 items-center px-1.5 align-middle rounded-default-xs border-0 bg-sunken text-[13px] font-bold leading-4 text-ink whitespace-nowrap hover:opacity-80"
className="inline-flex h-6 max-w-full gap-1.5 items-center px-1.5 align-middle rounded-default-xs border-0 bg-sunken text-[13px] font-bold leading-4 text-ink whitespace-nowrap hover:opacity-80"
>
<BotAvatar
image_media_id={bot.image_media_id}
@@ -43,9 +43,10 @@ export default function BotRefChip({ bot_id }: Props) {
face={(bot.face || "idle") as BotFace}
label={bot.name || ""}
size={18}
className="shrink-0"
/>
{bot.name}
{workspace_id && bot.is_admin ? <AdminShield size={13} /> : null}
<Span className="min-w-0 truncate">{bot.name}</Span>
{workspace_id && bot.is_admin ? <AdminShield size={13} className="shrink-0" /> : null}
</Link>
);
}
@@ -38,14 +38,15 @@ export default function ComputerRefChip({ computer_id }: Props) {
openComputer({ computer_id: computer.id || 0, mode: "browser" })
}
className={twMerge(
"inline-flex h-6 gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold whitespace-nowrap text-ink dark:text-ink",
"inline-flex h-6 max-w-full gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold whitespace-nowrap text-ink dark:text-ink",
"[&_.twui-button-content-wrapper]:min-w-0",
current
? "bg-sunken hover:bg-sunken outline-0"
: "bg-transparent hover:bg-sunken",
)}
>
<Monitor size={14} aria-hidden className="shrink-0" />
{name}
<Span className="min-w-0 truncate">{name}</Span>
{current ? <Check size={14} aria-hidden className="shrink-0" /> : null}
</Button>
);
@@ -22,7 +22,7 @@ type Props = {
*/
function CrewMessageLine({ message }: Props) {
return (
<Stack className="w-full gap-2.5! items-start break-words">
<Stack className="w-full gap-2.5! items-start wrap-anywhere [&>*]:max-w-full">
<Markdown remarkPlugins={RemarkPlugins} components={MessageMarkdownComponents}>
{linkComputerRefs({ text: message.text || "" })}
</Markdown>
@@ -1,4 +1,5 @@
import { useState } from "react";
import { twMerge } from "tailwind-merge";
import Modal from "@moduletrace/twui/elements/Modal";
import Button from "@moduletrace/twui/layout/Button";
import H2 from "@moduletrace/twui/layout/H2";
@@ -43,13 +44,16 @@ export default function HookProposalRefChip({ proposal_id }: Props) {
color="gray"
size="smaller"
title={`Review: ${title}`}
className="inline-flex h-6 gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap"
className={twMerge(
"inline-flex h-6 max-w-full gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap",
"[&_.twui-button-content-wrapper]:min-w-0",
)}
>
<Webhook size={14} aria-hidden className="shrink-0" />
{`${title} · ${status}`}
<Span className="min-w-0 truncate">{`${title} · ${status}`}</Span>
</Button>
}
targetWrapperProps={{ className: "inline-flex align-middle" }}
targetWrapperProps={{ className: "inline-flex max-w-full align-middle" }}
open={open}
setOpen={setOpen}
className="max-w-xl bg-paper p-6"
@@ -121,7 +121,7 @@ export default function MarkdownLink({ href, children }: ComponentProps<"a">) {
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-ink underline underline-offset-2 hover:opacity-80 break-words"
className="text-ink underline underline-offset-2 hover:opacity-80 wrap-anywhere"
>
{children}
</Link>
@@ -26,7 +26,7 @@ export default function MarkdownList({ children, ordered, start, className }: Pr
start={start}
items={items}
className={twMerge(
`w-full gap-1! pl-5 my-0 text-sm leading-5 text-ink ${ordered ? "list-decimal" : "list-disc"}`,
`self-stretch gap-1! pl-5 my-0 text-sm leading-5 text-ink [&>li]:max-w-full ${ordered ? "list-decimal" : "list-disc"}`,
className,
)}
/>
@@ -1,4 +1,5 @@
import { useState } from "react";
import { twMerge } from "tailwind-merge";
import Modal from "@moduletrace/twui/elements/Modal";
import Button from "@moduletrace/twui/layout/Button";
import H2 from "@moduletrace/twui/layout/H2";
@@ -47,13 +48,16 @@ export default function ProposalRefChip({ proposal_id }: Props) {
color="gray"
size="smaller"
title={`Review: ${title}`}
className="inline-flex h-6 gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap"
className={twMerge(
"inline-flex h-6 max-w-full gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap",
"[&_.twui-button-content-wrapper]:min-w-0",
)}
>
<Icon size={14} aria-hidden className="shrink-0" />
{`${title} · ${status}`}
<Span className="min-w-0 truncate">{`${title} · ${status}`}</Span>
</Button>
}
targetWrapperProps={{ className: "inline-flex align-middle" }}
targetWrapperProps={{ className: "inline-flex max-w-full align-middle" }}
open={open}
setOpen={setOpen}
className="max-w-xl bg-paper p-6"
@@ -1,4 +1,5 @@
import { useState } from "react";
import { twMerge } from "tailwind-merge";
import Modal from "@moduletrace/twui/elements/Modal";
import Button from "@moduletrace/twui/layout/Button";
import H2 from "@moduletrace/twui/layout/H2";
@@ -46,13 +47,16 @@ export default function SecretRequestRefChip({ request_id }: Props) {
color="gray"
size="smaller"
title={view.status == "pending" ? `Enter ${view.key}` : `${view.key}: ${status}`}
className="inline-flex h-6 gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap"
className={twMerge(
"inline-flex h-6 max-w-full gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap",
"[&_.twui-button-content-wrapper]:min-w-0",
)}
>
<KeyRound size={14} aria-hidden className="shrink-0" />
{`${view.key} · ${status}`}
<Span className="min-w-0 truncate">{`${view.key} · ${status}`}</Span>
</Button>
}
targetWrapperProps={{ className: "inline-flex align-middle" }}
targetWrapperProps={{ className: "inline-flex max-w-full align-middle" }}
open={open}
setOpen={setOpen}
className="max-w-xl bg-paper p-6"
@@ -1,4 +1,5 @@
import { useContext } from "react";
import { twMerge } from "tailwind-merge";
import Button from "@moduletrace/twui/layout/Button";
import Span from "@moduletrace/twui/layout/Span";
import { Hand } from "lucide-react";
@@ -34,10 +35,13 @@ export default function TakeOverChip({ computer_id, mode }: Props) {
size="smaller"
title={`Open ${name} (${mode_title}) and take over`}
onClick={() => openComputer({ computer_id, mode, take_over: true })}
className="inline-flex h-6 gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap"
className={twMerge(
"inline-flex h-6 max-w-full gap-1.5! flex-nowrap align-middle px-2! text-[13px] font-bold text-ink whitespace-nowrap",
"[&_.twui-button-content-wrapper]:min-w-0",
)}
>
<Hand size={14} aria-hidden className="shrink-0" />
{`Take over ${name} · ${mode_title}`}
<Span className="min-w-0 truncate">{`Take over ${name} · ${mode_title}`}</Span>
</Button>
);
}
@@ -25,7 +25,7 @@ export default function UserBubble({ text }: { text: string }) {
{...{ [QuotableAttribute]: "" }}
className={twMerge(
"max-w-[80%] min-w-0 gap-2.5! items-stretch rounded-default-lg",
"bg-ink text-on-ink px-4 py-3 text-sm leading-5 break-words",
"bg-ink text-on-ink px-4 py-3 text-sm leading-5 wrap-anywhere",
"conversation-user-bubble",
)}
>
@@ -14,7 +14,7 @@ export default function UserMarkdownLink(props: ComponentProps<"a">) {
href={props.href}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 dark:text-blue-600 underline underline-offset-2 hover:opacity-80 break-words"
className="text-blue-400 dark:text-blue-600 underline underline-offset-2 hover:opacity-80 wrap-anywhere"
>
{props.children}
</Link>
@@ -24,10 +24,10 @@ export default function WorkspaceRefChip({ workspace_id }: Props) {
<Link
href={`/admin/workspaces/${workspace.id}`}
title={`Open ${workspace.name}`}
className="inline-flex h-6 gap-1.5 items-center px-1.5 align-middle rounded-default-xs border-0 bg-sunken text-[13px] font-bold leading-4 text-ink whitespace-nowrap hover:opacity-80"
className="inline-flex h-6 max-w-full gap-1.5 items-center px-1.5 align-middle rounded-default-xs border-0 bg-sunken text-[13px] font-bold leading-4 text-ink whitespace-nowrap hover:opacity-80"
>
<WorkspaceAvatar image_media_id={workspace.image_media_id} size="xs" />
{workspace.name}
<Span className="min-w-0 truncate">{workspace.name}</Span>
</Link>
);
}