惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
阮一峰的网络日志
阮一峰的网络日志
博客园 - 聂微东
F
Fortinet All Blogs
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
DataBreaches.Net
MyScale Blog
MyScale Blog
B
Blog
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
GbyAI
GbyAI
Google DeepMind News
Google DeepMind News
IT之家
IT之家
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
博客园_首页
L
LangChain Blog
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - Franky

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(ui): prevent chat actions overlapping replies · openc...
JARVIS-Glass · 2026-05-13 · via Recent Commits to openclaw:main

@@ -68,6 +68,19 @@ function iconSvg() {

6868

return `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"></path></svg>`;

6969

}

707071+

function chatBubbleActionsHtml() {

72+

return `

73+

<div class="chat-bubble-actions">

74+

<button class="btn btn--xs chat-expand-btn" type="button" aria-label="Open in canvas">

75+

<span class="chat-expand-btn__icon" aria-hidden="true">${iconSvg()}</span>

76+

</button>

77+

<button class="btn btn--xs chat-copy-btn" type="button" aria-label="Copy as markdown">

78+

<span class="chat-copy-btn__icon" aria-hidden="true">${iconSvg()}</span>

79+

</button>

80+

</div>

81+

`;

82+

}

83+7184

function chatControlsHtml(opts: { agent?: boolean } = {}) {

7285

const showAgent = opts.agent !== false;

7386

return `

@@ -168,6 +181,7 @@ function chatHtml(opts: { sideResult?: boolean; singleAgent?: boolean } = {}) {

168181

<div class="chat-avatar assistant">A</div>

169182

<div class="chat-group-messages">

170183

<div class="chat-bubble has-copy">

184+

${chatBubbleActionsHtml()}

171185

<div class="chat-text">

172186

<p>The chat shell should stay compact and readable.</p>

173187

<pre><code>const importantLongIdentifier = "control-ui-chat-responsive-regression-fixture-keeps-code-scrollable"; console.log(importantLongIdentifier);</code></pre>

@@ -226,6 +240,41 @@ async function openFixture(

226240

return page;

227241

}

228242243+

async function getRect(page: Page, selector: string) {

244+

const rect = await page.locator(selector).evaluate((node) => {

245+

const bounds = (node as HTMLElement).getBoundingClientRect();

246+

return {

247+

left: bounds.left,

248+

right: bounds.right,

249+

top: bounds.top,

250+

bottom: bounds.bottom,

251+

width: bounds.width,

252+

height: bounds.height,

253+

};

254+

});

255+

expectFiniteRect({ x: rect.left, y: rect.top, width: rect.width, height: rect.height });

256+

return rect;

257+

}

258+259+

async function getTextContentRect(page: Page, selector: string) {

260+

const rect = await page.locator(selector).evaluate((node) => {

261+

const range = document.createRange();

262+

range.selectNodeContents(node);

263+

const bounds = range.getBoundingClientRect();

264+

range.detach();

265+

return {

266+

left: bounds.left,

267+

right: bounds.right,

268+

top: bounds.top,

269+

bottom: bounds.bottom,

270+

width: bounds.width,

271+

height: bounds.height,

272+

};

273+

});

274+

expectFiniteRect({ x: rect.left, y: rect.top, width: rect.width, height: rect.height });

275+

return rect;

276+

}

277+229278

async function openHeaderFixture(width: number, height: number, opts: { hidden?: boolean } = {}) {

230279

const page = await browser.newPage({ viewport: { width, height } });

231280

await page.setContent(

@@ -326,6 +375,42 @@ describeBrowserLayout("chat responsive browser layout", () => {

326375

}

327376

});

328377378+

it.each([

379+

[320, 568],

380+

[1366, 900],

381+

] as const)(

382+

"keeps short assistant text clear of bubble actions at %sx%s",

383+

async (width, height) => {

384+

const page = await browser.newPage({ viewport: { width, height } });

385+

try {

386+

await page.setContent(

387+

`<!doctype html><html><head><style>${readUiCss()}</style></head><body>

388+

<div class="chat-thread" role="log">

389+

<div class="chat-thread-inner">

390+

<div class="chat-group assistant">

391+

<div class="chat-avatar assistant">A</div>

392+

<div class="chat-group-messages">

393+

<div class="chat-bubble has-copy">

394+

${chatBubbleActionsHtml()}

395+

<div class="chat-text"><p>Done.</p></div>

396+

</div>

397+

</div>

398+

</div>

399+

</div>

400+

</div>

401+

</body></html>`,

402+

);

403+

await page.locator(".chat-bubble").hover();

404+405+

const text = await getTextContentRect(page, ".chat-text p");

406+

const actions = await getRect(page, ".chat-bubble-actions");

407+

expect(text.right).toBeLessThanOrEqual(actions.left - 1);

408+

} finally {

409+

await page.close();

410+

}

411+

},

412+

);

413+329414

it.each(["dark", "light"] as const)(

330415

"keeps mobile controls inside the viewport with touch targets in %s mode",

331416

async (themeMode) => {