mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
improve(ui): even out transcript turn and run block spacing (#128298)
* improve(ui): even out transcript turn and run block spacing * fix(ui): keep expanded tool rows compact * mock: fixtures (drop before land) * test(ui): lock transcript spacing rhythm * test(ui): cover expanded tool row spacing * test(ui): cover touch footer turn spacing * fix(ui): keep touch turn gaps stable with footer actions * fix(ui): preserve persistent footer turn spacing * fix(ui): preserve revealed footer turn spacing
This commit is contained in:
committed by
GitHub
parent
b0b7dec126
commit
09eb2f9934
@@ -289,6 +289,80 @@ function completedWorkSpacingHtml() {
|
||||
`;
|
||||
}
|
||||
|
||||
function runBlockSpacingHtml() {
|
||||
return `
|
||||
<div class="chat-thread chat-thread--direct" role="log">
|
||||
<div class="chat-thread-inner">
|
||||
<div class="chat-group assistant chat-group--with-footer" data-run-turn>
|
||||
<div class="chat-group-messages">
|
||||
<div class="chat-bubble" data-run-block="text"><div class="chat-text">Opening text</div></div>
|
||||
<div class="chat-bubble" data-run-block="detail"><div class="chat-text">Detail text</div></div>
|
||||
<div class="chat-bubble chat-bubble--tool-shell" data-run-block="tool">Tool row</div>
|
||||
<div class="chat-activity-group" data-run-block="list">
|
||||
<div class="chat-activity-group__body">
|
||||
<div class="chat-group-messages">
|
||||
<div class="chat-bubble" data-expanded-row="text">Expanded detail</div>
|
||||
<div class="chat-bubble chat-bubble--tool-shell" data-expanded-row="tool">Expanded tool row</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-activity-group chat-work-group" data-run-block="work">
|
||||
<button class="chat-inline-disclosure chat-activity-group__summary" type="button">Worked for 10s</button>
|
||||
<div class="chat-work-group__separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-group-footer">
|
||||
<span class="chat-sender-name">Assistant</span>
|
||||
<div class="chat-group-footer-actions">
|
||||
<button class="chat-copy-btn" type="button" aria-label="Copy">${iconSvg()}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-group user chat-group--with-footer" data-next-turn>
|
||||
<div class="chat-group-messages">
|
||||
<div class="chat-bubble"><div class="chat-text">Next turn</div></div>
|
||||
</div>
|
||||
<div class="chat-group-footer"><span class="chat-sender-name">You</span></div>
|
||||
</div>
|
||||
<div class="chat-group user chat-group--with-footer" data-persistent-turn>
|
||||
<div class="chat-group-messages">
|
||||
<div class="chat-bubble"><div class="chat-text">Persistent identity turn</div></div>
|
||||
</div>
|
||||
<div class="chat-group-footer chat-group-footer--persistent-identity">
|
||||
<span class="chat-sender-name">You</span>
|
||||
<div class="chat-group-footer-actions">
|
||||
<button class="chat-copy-btn" type="button" aria-label="Copy">${iconSvg()}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-group assistant chat-group--with-footer" data-after-persistent-turn>
|
||||
<div class="chat-group-messages">
|
||||
<div class="chat-bubble"><div class="chat-text">After persistent identity</div></div>
|
||||
</div>
|
||||
<div class="chat-group-footer"><span class="chat-sender-name">Assistant</span></div>
|
||||
</div>
|
||||
<div class="chat-group user chat-group--with-footer chat-group--meta-revealed" data-revealed-persistent-turn>
|
||||
<div class="chat-group-messages">
|
||||
<div class="chat-bubble"><div class="chat-text">Revealed persistent identity</div></div>
|
||||
</div>
|
||||
<div class="chat-group-footer chat-group-footer--persistent-identity">
|
||||
<span class="chat-sender-name">You</span>
|
||||
<div class="chat-group-footer-actions">
|
||||
<button class="chat-copy-btn" type="button" aria-label="Copy">${iconSvg()}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-group assistant chat-group--with-footer" data-after-revealed-turn>
|
||||
<div class="chat-group-messages">
|
||||
<div class="chat-bubble"><div class="chat-text">After revealed identity</div></div>
|
||||
</div>
|
||||
<div class="chat-group-footer"><span class="chat-sender-name">Assistant</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function chatFooterActionsHtml() {
|
||||
return `
|
||||
<div class="chat-group-footer-actions">
|
||||
@@ -1307,6 +1381,63 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ label: "desktop", width: 1366, hasTouch: false },
|
||||
{ label: "mobile", width: 430, hasTouch: true },
|
||||
])("keeps transcript turn and run block spacing on $label", async ({ width, hasTouch }) => {
|
||||
const page = await openBrowserPage(width, 900, { hasTouch, isolated: true });
|
||||
try {
|
||||
await page.setContent(
|
||||
`<!doctype html><html><head><style>${readUiCss()}</style></head><body>${runBlockSpacingHtml()}</body></html>`,
|
||||
);
|
||||
|
||||
const gaps = await page.evaluate(() => {
|
||||
const rect = (selector: string) =>
|
||||
document.querySelector<HTMLElement>(selector)!.getBoundingClientRect();
|
||||
const gap = (before: string, after: string) => rect(after).top - rect(before).bottom;
|
||||
return {
|
||||
intraTurn: gap('[data-run-block="text"]', '[data-run-block="detail"]'),
|
||||
textToTool: gap('[data-run-block="detail"]', '[data-run-block="tool"]'),
|
||||
toolToList: gap('[data-run-block="tool"]', '[data-run-block="list"]'),
|
||||
listToWork: gap('[data-run-block="list"]', '[data-run-block="work"]'),
|
||||
expandedTextToTool: gap('[data-expanded-row="text"]', '[data-expanded-row="tool"]'),
|
||||
workedForSeparator: gap(
|
||||
'[data-run-block="work"] > button',
|
||||
".chat-work-group__separator",
|
||||
),
|
||||
turn: gap('[data-run-block="work"]', "[data-next-turn] .chat-bubble"),
|
||||
persistentTurn: gap(
|
||||
"[data-persistent-turn] .chat-bubble",
|
||||
"[data-after-persistent-turn] .chat-bubble",
|
||||
),
|
||||
revealedPersistentTurn: gap(
|
||||
"[data-revealed-persistent-turn] .chat-bubble",
|
||||
"[data-after-revealed-turn] .chat-bubble",
|
||||
),
|
||||
simpleToPersistentTurn: gap(
|
||||
"[data-next-turn] .chat-bubble",
|
||||
"[data-persistent-turn] .chat-bubble",
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
expect(gaps).toEqual({
|
||||
intraTurn: 2,
|
||||
textToTool: 12,
|
||||
toolToList: 12,
|
||||
listToWork: 12,
|
||||
expandedTextToTool: 6,
|
||||
workedForSeparator: 0,
|
||||
turn: 50,
|
||||
persistentTurn: 50,
|
||||
revealedPersistentTurn: 50,
|
||||
simpleToPersistentTurn: 50,
|
||||
});
|
||||
} finally {
|
||||
await closeBrowserPage(page);
|
||||
}
|
||||
});
|
||||
|
||||
it("insets only the bundled logo inside the unchanged avatar box", async () => {
|
||||
const page = await openBrowserPage(430, 720);
|
||||
try {
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
============================================= */
|
||||
|
||||
/* Chat Group Layout - default (assistant/other on left). Groups without a
|
||||
footer keep the shared 26px rhythm directly. Footer-bearing groups split
|
||||
footer keep the shared turn gap directly. Footer-bearing groups split
|
||||
message content and footer into separate rows so each row keeps the avatar
|
||||
gutter while only the message row controls avatar alignment. */
|
||||
.chat-group {
|
||||
--chat-footer-row-offset: 26px;
|
||||
--chat-message-column-max: var(--chat-message-max-width, min(900px, 68%));
|
||||
--chat-run-block-gap: 12px;
|
||||
--chat-touch-row-margin: 14px;
|
||||
--chat-turn-gap: 50px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
padding-bottom: 26px;
|
||||
padding-bottom: var(--chat-turn-gap);
|
||||
margin-left: 4px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
@@ -55,7 +59,7 @@
|
||||
grid-template-rows: auto auto;
|
||||
gap: 2px 10px;
|
||||
justify-content: start;
|
||||
padding-bottom: 0;
|
||||
padding-bottom: calc(var(--chat-turn-gap) - var(--chat-footer-row-offset));
|
||||
}
|
||||
|
||||
.chat-group.user.chat-group--with-footer {
|
||||
@@ -110,6 +114,21 @@
|
||||
margin-block-start: var(--chat-message-type-boundary-gap);
|
||||
}
|
||||
|
||||
.chat-group
|
||||
> .chat-group-messages
|
||||
> :is(
|
||||
.chat-bubble--tool-shell + .chat-bubble:not(.chat-bubble--tool-shell),
|
||||
.chat-bubble:not(.chat-bubble--tool-shell) + .chat-bubble--tool-shell,
|
||||
.chat-bubble + .chat-activity-group,
|
||||
.chat-activity-group + .chat-bubble,
|
||||
.chat-work-group + .chat-bubble,
|
||||
.chat-work-group + .chat-activity-group,
|
||||
.chat-bubble + .chat-work-group,
|
||||
.chat-activity-group + .chat-work-group
|
||||
) {
|
||||
margin-block-start: calc(var(--chat-run-block-gap) - 2px);
|
||||
}
|
||||
|
||||
/* User messages align content right */
|
||||
.chat-group.user .chat-group-messages {
|
||||
align-items: flex-end;
|
||||
@@ -746,11 +765,22 @@ img.chat-avatar.chat-avatar--logo {
|
||||
/* Shared message renderers stay touch-readable by default. Transcript rows
|
||||
opt into one-at-a-time disclosure through their owning thread. */
|
||||
.chat-group {
|
||||
--chat-footer-row-offset: 44px;
|
||||
--chat-footer-disclosure-opacity: 1;
|
||||
--chat-footer-disclosure-pointer-events: auto;
|
||||
--chat-footer-detail-position: static;
|
||||
padding-bottom: calc(var(--chat-turn-gap) - var(--chat-touch-row-margin));
|
||||
margin-bottom: var(--chat-touch-row-margin);
|
||||
}
|
||||
|
||||
.chat-group.chat-group--with-footer:has(
|
||||
.chat-group-footer:not(.chat-group-footer--persistent-identity) .chat-group-footer-actions
|
||||
),
|
||||
.chat-group.chat-group--with-footer:is(.chat-group--meta-revealed, :focus-within):has(
|
||||
.chat-group-footer--persistent-identity .chat-group-footer-actions
|
||||
) {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat-group-footer {
|
||||
|
||||
Reference in New Issue
Block a user