diff --git a/scripts/docs-site/docs-layout.css b/scripts/docs-site/docs-layout.css
index 471a9968be..d416bd0ee1 100644
--- a/scripts/docs-site/docs-layout.css
+++ b/scripts/docs-site/docs-layout.css
@@ -207,6 +207,10 @@
@media (prefers-reduced-motion:reduce) {
.docs-layout .doc .release-source-toggle > summary::before { transition: none; }}
.docs-layout .doc .release-source-toggle > summary:focus-visible { outline: 2px solid var(--oc-focus-ring); outline-offset: 2px; }
+/* Patch lists keep their detail control in the sentence's inline flow. */
+.docs-layout .doc li:has(> .release-source-inline) > p:first-child { display: inline; margin: 0; }
+.docs-layout .doc .release-source-inline { display: inline; }
+.docs-layout .doc .release-source-inline > summary { display: inline-flex; vertical-align: baseline; margin-inline-start: .35em; }
@media (hover:hover) and (pointer:fine) {
.docs-layout .doc .release-source-toggle > summary:hover::before { color: var(--ink); }}
.docs-layout .doc > :is(p,ul,ol,blockquote) { max-inline-size: var(--docs-prose-measure); margin-block: 0 var(--docs-paragraph-gap); }
diff --git a/scripts/docs-site/release-inline-details.test.mjs b/scripts/docs-site/release-inline-details.test.mjs
new file mode 100644
index 0000000000..9e74fbf845
--- /dev/null
+++ b/scripts/docs-site/release-inline-details.test.mjs
@@ -0,0 +1,59 @@
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import { test } from "node:test";
+import { chromium } from "playwright";
+import { createMarkdownRenderer, renderMdxish } from "./mdx-ish.mjs";
+
+test("patch details follow the fix inline and retain native keyboard access", async () => {
+ const browser = await chromium.launch({ headless: true });
+ const source = `- **Conversation recovery.** Fixes replies getting stuck after an update.
+
+
+ More details
+
+ [#123](https://github.com/openclaw/openclaw/pull/123), thanks to [@example](https://github.com/example).
+
+
+
+- An unrelated list item.
+`;
+ const css = fs.readFileSync(new URL("./docs-layout.css", import.meta.url), "utf8");
+ try {
+ for (const width of [1200, 390]) {
+ const page = await browser.newPage({ viewport: { width, height: 700 } });
+ try {
+ await page.setContent(`
${renderMdxish(source, createMarkdownRenderer())}
`);
+ const summary = page.locator(".release-source-inline > summary");
+ const link = page.getByRole("link", { name: "#123", exact: true });
+ assert.equal(await link.isVisible(), false);
+ const layout = await summary.evaluate((element) => {
+ const paragraph = element.parentElement.previousElementSibling;
+ const range = document.createRange();
+ range.setStart(paragraph.lastChild, paragraph.lastChild.textContent.length - 1);
+ range.setEnd(paragraph.lastChild, paragraph.lastChild.textContent.length);
+ const end = range.getBoundingClientRect();
+ const control = element.getBoundingClientRect();
+ return { end: { left: end.left, right: end.right, top: end.top, bottom: end.bottom }, control: { left: control.left, top: control.top, bottom: control.bottom, height: control.height }, overflow: document.documentElement.scrollWidth > innerWidth, paragraphDisplay: getComputedStyle(paragraph).display };
+ });
+ assert.equal(layout.paragraphDisplay, "inline");
+ assert.ok(layout.control.height >= 44, "retain the existing touch target");
+ assert.equal(layout.overflow, false, "narrow view must not scroll horizontally");
+ if (width === 1200) {
+ assert.ok(layout.control.left >= layout.end.right, "control follows the final character");
+ assert.ok(layout.control.top < layout.end.bottom && layout.control.bottom > layout.end.top, "control and sentence share a line");
+ }
+ await summary.click();
+ assert.equal(await link.isVisible(), true);
+ await summary.focus();
+ await page.keyboard.press("Space");
+ assert.equal(await link.isVisible(), false);
+ await page.keyboard.press("Enter");
+ assert.equal(await link.isVisible(), true);
+ await page.keyboard.press("Tab");
+ assert.equal(await link.evaluate((element) => element === document.activeElement), true);
+ await page.keyboard.press("Shift+Tab");
+ assert.equal(await summary.evaluate((element) => element === document.activeElement), true);
+ } finally { await page.close(); }
+ }
+ } finally { await browser.close(); }
+});