diff --git a/src/renderer/src/components/editor/RichMarkdownEditorSurface.tsx b/src/renderer/src/components/editor/RichMarkdownEditorSurface.tsx
index 12bc5cc35c3..8e2ca1e4abb 100644
--- a/src/renderer/src/components/editor/RichMarkdownEditorSurface.tsx
+++ b/src/renderer/src/components/editor/RichMarkdownEditorSurface.tsx
@@ -206,7 +206,8 @@ export function RichMarkdownEditorSurface({
{
if (!shouldFocusEmptyEditorFromSurfaceClick(event, editor)) {
return
diff --git a/tests/e2e/markdown-tab-scroll-restore.spec.ts b/tests/e2e/markdown-tab-scroll-restore.spec.ts
new file mode 100644
index 00000000000..02362dd822a
--- /dev/null
+++ b/tests/e2e/markdown-tab-scroll-restore.spec.ts
@@ -0,0 +1,103 @@
+import { writeFile, rm } from 'node:fs/promises'
+import path from 'node:path'
+import { test, expect } from './helpers/orca-app'
+import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
+import {
+ cleanupMarkdownFixture,
+ createMarkdownFixture,
+ getActiveWorktreeContext,
+ openMarkdownFixture,
+ waitForRichMarkdownEditor
+} from './helpers/markdown-editor-fixture'
+
+test('restores the Markdown viewport when an image gains height after a tab switch', async ({
+ orcaPage
+}, testInfo) => {
+ await waitForSessionReady(orcaPage)
+ await waitForActiveWorktree(orcaPage)
+ const context = await getActiveWorktreeContext(orcaPage)
+ const directory = '.orca-e2e-markdown-scroll'
+ let filePath: string | null = null
+ let otherPath: string | null = null
+ let imagePath: string | null = null
+
+ try {
+ const sections = Array.from(
+ { length: 100 },
+ (_, index) => `## Section ${index}\n\nParagraph ${index}. Scroll restoration testing text.`
+ ).join('\n\n')
+ filePath = await createMarkdownFixture(
+ context,
+ directory,
+ 'image-scroll',
+ testInfo.workerIndex,
+ `# Image scroll\n\n\n\n${sections}`
+ )
+ imagePath = path.join(path.dirname(filePath), 'tall.svg')
+ await writeFile(
+ imagePath,
+ ''
+ )
+ otherPath = await createMarkdownFixture(
+ context,
+ directory,
+ 'other-tab',
+ testInfo.workerIndex,
+ '# Other tab'
+ )
+ await openMarkdownFixture(orcaPage, context, otherPath)
+ await waitForRichMarkdownEditor(orcaPage)
+ await openMarkdownFixture(orcaPage, context, filePath)
+ const editor = await waitForRichMarkdownEditor(orcaPage)
+ const image = editor.getByRole('img', { name: 'Scroll restoration image' })
+ await expect
+ .poll(() =>
+ image.evaluate((element) =>
+ element instanceof HTMLImageElement ? element.naturalHeight : 0
+ )
+ )
+ .toBe(1500)
+ const viewport = orcaPage.locator('.rich-markdown-editor-shell .overflow-auto')
+ await viewport.evaluate((element) => {
+ element.scrollTop = 4000
+ })
+ const heading = editor.getByRole('heading', { name: 'Section 45', exact: true })
+ const originalTop = await heading.evaluate((element) => element.getBoundingClientRect().top)
+
+ await orcaPage
+ .locator('[data-tab-id]')
+ .filter({ hasText: path.basename(otherPath) })
+ .click()
+ // Model image dimensions arriving after restoration, independent of the host's decode speed.
+ const pendingImage = await orcaPage.addStyleTag({
+ content:
+ '.rich-markdown-editor img[alt="Scroll restoration image"] { height: 1px !important; }'
+ })
+ await orcaPage
+ .locator('[data-tab-id]')
+ .filter({ hasText: path.basename(filePath) })
+ .click()
+ await expect.poll(() => viewport.evaluate((element) => element.scrollTop)).toBe(4000)
+ await pendingImage.evaluate((element) => element.remove())
+ await expect
+ .poll(() => image.evaluate((element) => element.getBoundingClientRect().height))
+ .toBeGreaterThan(500)
+ // Let Chromium apply its scroll-anchor adjustment before checking the final viewport.
+ await viewport.evaluate(
+ () =>
+ new Promise((resolve) => {
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
+ })
+ )
+ await expect.poll(() => viewport.evaluate((element) => element.scrollTop)).toBe(4000)
+ await expect
+ .poll(() => heading.evaluate((element) => element.getBoundingClientRect().top))
+ .toBe(originalTop)
+ } finally {
+ await cleanupMarkdownFixture(filePath)
+ await cleanupMarkdownFixture(otherPath)
+ if (imagePath) {
+ await rm(imagePath, { force: true })
+ }
+ }
+})