mirror of
https://github.com/block/buzz.git
synced 2026-11-01 23:11:58 +00:00
fix(desktop): align agent message metadata
Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
@@ -209,7 +209,7 @@ export function InboxMessageRow({
|
||||
<div className="min-w-0 flex-1">
|
||||
{isContinuation ? null : (
|
||||
<div
|
||||
className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0"
|
||||
className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0"
|
||||
data-testid="message-header"
|
||||
>
|
||||
<UserProfilePopover
|
||||
@@ -236,7 +236,7 @@ export function InboxMessageRow({
|
||||
start of a line on its own. Gap matches the container's, so
|
||||
spacing reads the same either side of the divider.
|
||||
*/}
|
||||
<span className="inline-flex min-w-0 items-center gap-x-2">
|
||||
<span className="inline-flex min-w-0 items-baseline gap-x-2">
|
||||
<MessageMetaSeparator />
|
||||
{timestampNode}
|
||||
</span>
|
||||
|
||||
@@ -11,30 +11,13 @@ export function MessageAgentOwner({
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
className="inline-flex min-w-0 max-w-56 items-baseline gap-1 text-xs leading-4 text-muted-foreground/65"
|
||||
className="inline-flex min-w-0 max-w-56 items-baseline gap-1 text-message-timestamp text-muted-foreground/65"
|
||||
data-testid="message-agent-owner"
|
||||
>
|
||||
<span className="sr-only">
|
||||
{ownerLabel ? "Agent managed by" : "Agent; owner unavailable"}
|
||||
</span>
|
||||
{/*
|
||||
* Icon and label sit directly in this baseline row rather than in a nested
|
||||
* flex wrapper, so the label's own baseline is what aligns with the author
|
||||
* name beside it. Both branches share the icon for the same reason: two
|
||||
* wrappers meant two alignment rules and the "owner unavailable" variant
|
||||
* had drifted a pixel off the other one.
|
||||
*
|
||||
* `self-center` keeps the icon out of baseline alignment, so the label —
|
||||
* not the icon's box — sets this chip's baseline. Centred on the line box
|
||||
* the glyph's ink still rides ~1.6px above the text's cap band, reading as
|
||||
* a couple of pixels too high; 0.125em drops its optical centre onto that
|
||||
* band. In em so it holds under Cmd +/- zoom, and as a transform so it
|
||||
* shifts nothing else in the row.
|
||||
*/}
|
||||
<Bot
|
||||
aria-hidden="true"
|
||||
className="h-3.5 w-3.5 shrink-0 translate-y-[0.125em] self-center"
|
||||
/>
|
||||
<Bot aria-hidden="true" className="h-3.5 w-3.5 shrink-0 self-center" />
|
||||
{ownerPubkey && ownerLabel ? (
|
||||
<>
|
||||
<span aria-hidden="true" className="shrink-0">
|
||||
|
||||
@@ -49,6 +49,18 @@ async function expectThreadReplyUnobscured(row: Locator) {
|
||||
.toBe(true);
|
||||
}
|
||||
|
||||
async function measureInlineBaseline(locator: Locator) {
|
||||
return locator.evaluate((element) => {
|
||||
const marker = document.createElement("span");
|
||||
marker.style.cssText =
|
||||
"display:inline-block;width:0;height:0;margin:0;padding:0;border:0";
|
||||
element.append(marker);
|
||||
const baseline = marker.getBoundingClientRect().top;
|
||||
marker.remove();
|
||||
return baseline;
|
||||
});
|
||||
}
|
||||
|
||||
async function measureThreadSummaryGeometry(summaryRow: Locator) {
|
||||
return summaryRow.evaluate((summaryButton) => {
|
||||
const summaryWrapper = summaryButton.parentElement;
|
||||
@@ -396,6 +408,64 @@ test("agent owner label identifies the agent and owner", async ({ page }) => {
|
||||
await expect(ownerTreatment.locator(".sr-only")).toHaveText(
|
||||
"Agent managed by",
|
||||
);
|
||||
|
||||
const author = aliceMessage.getByTestId("message-author");
|
||||
const managedBy = ownerTreatment.getByText("managed by", { exact: true });
|
||||
const owner = ownerTreatment.locator(".font-semibold");
|
||||
const timestamp = aliceMessage.getByTestId("message-timestamp");
|
||||
|
||||
for (const fontSize of ["smaller", "default", "larger"]) {
|
||||
await page.evaluate((value) => {
|
||||
document.documentElement.dataset.fontSize = value;
|
||||
}, fontSize);
|
||||
|
||||
const [
|
||||
authorBaseline,
|
||||
managedByBaseline,
|
||||
ownerBaseline,
|
||||
timestampBaseline,
|
||||
] = await Promise.all([
|
||||
measureInlineBaseline(author),
|
||||
measureInlineBaseline(managedBy),
|
||||
measureInlineBaseline(owner),
|
||||
measureInlineBaseline(timestamp),
|
||||
]);
|
||||
const [ownerTypography, timestampTypography] = await Promise.all([
|
||||
ownerTreatment.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
return { fontSize: style.fontSize, lineHeight: style.lineHeight };
|
||||
}),
|
||||
timestamp.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
return { fontSize: style.fontSize, lineHeight: style.lineHeight };
|
||||
}),
|
||||
]);
|
||||
const iconCenterOffset = await ownerTreatment.evaluate((element) => {
|
||||
const icon = element.querySelector("svg");
|
||||
if (!icon) throw new Error("Expected the agent owner icon.");
|
||||
const ownerRect = element.getBoundingClientRect();
|
||||
const iconRect = icon.getBoundingClientRect();
|
||||
return (
|
||||
iconRect.top +
|
||||
iconRect.height / 2 -
|
||||
(ownerRect.top + ownerRect.height / 2)
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
{ managedByBaseline, ownerBaseline, timestampBaseline },
|
||||
`message header baselines at ${fontSize} font size`,
|
||||
).toEqual({
|
||||
managedByBaseline: authorBaseline,
|
||||
ownerBaseline: authorBaseline,
|
||||
timestampBaseline: authorBaseline,
|
||||
});
|
||||
expect(ownerTypography).toEqual(timestampTypography);
|
||||
expect(
|
||||
Math.abs(iconCenterOffset),
|
||||
`agent icon center at ${fontSize} font size`,
|
||||
).toBeLessThan(0.01);
|
||||
}
|
||||
});
|
||||
|
||||
test("send a message and see it in timeline", async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user