fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(web): return focus to the composer after closing a media preview - #9513

Merged
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus
Sep 4, 2026
Merged

fix(web): return focus to the composer after closing a media preview#9513
juliusmarminge merged 1 commit into
fix/composer-hold-for-selectionfrom
fix/image-preview-return-focus

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Reported: with the composer expanded and an attachment on it, opening the attachment keeps the composer up (correct), but closing the preview collapses it instead of leaving focus on the composer.

Opening the preview leaves focus on the thumbnail button, which is inside the composer, so the composer stays expanded. But interacting with the dialog — the close button, the image, a media control — moves focus into the portaled dialog, which is outside the composer's focus scope. When the dialog unmounts on close, focus falls to <body>; the composer's blur check sees nothing of its own focused and rests it. Reliable repro: attach an image with an empty prompt, open it, close it with the ✕.

Two changes to ExpandedImageDialog:

  • It marks itself as a composer-owned floating layer (composerFloatingLayerProps), so focus landing on the close button or a media control while the preview is open counts as still inside the composer scope — the same mechanism already used for composer menus and popovers.
  • On unmount it returns focus to whatever element opened it, so closing lands the caret back where the user was instead of on <body>.

Applies to image and video previews and every close path (✕, backdrop, Escape, or after interacting with the media). Stacked on #9499; the diff is independent.

Before

Empty prompt + attachment, open the preview, close with ✕ → composer collapses to its resting line.

before

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2ac3f3888c0ee100/before-preview.mp4

After

Same steps; the composer stays expanded with focus back on it.

after

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfb194be5223fd9f/after-preview.mp4

Surfaces

  • Web and desktop. The composerEventScope test already covers that a composer-owned floating layer counts as inside the composer scope.
  • Manual pass against a copy of real data: on main the composer collapsed 254px → 84px on every close; with the fix it stays 254px across ✕, backdrop, Escape, and after clicking the media, for both image and video.

Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

Note

Restore focus to previously active element when ExpandedImageDialog closes

Records the document's active element on mount and refocuses it on unmount using preventScroll, so closing a media preview returns focus to the composer. The portal root also now receives composerFloatingLayerProps.

Macroscope summarized f842ddf.

Opening an attachment preview from the composer left the composer
expanded, but closing it dropped focus on the removed dialog. The blur
check saw nothing of the composer's under focus and rested it. The
dialog now marks itself as composer-owned while open and hands focus
back to whatever opened it on close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB15.1 KiB
CodexThread snapshot wire7.0 KiB7.3 KiB
CodexLive turn WebSocket wire6.5 KiB7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB66.4 KiB
CodexLive turn messages821
ClaudeTotal thread wire13.6 KiB15.1 KiB
ClaudeThread snapshot wire7.0 KiB7.3 KiB
ClaudeLive turn WebSocket wire6.5 KiB7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB66.4 KiB
ClaudeLive turn messages921

Baseline: unavailable · PR result: f842ddf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f842ddf

Macroscope's review found this PR approvable — This is a focused one-file UI bug fix that restores focus after media-preview closure and reuses the existing composer focus-scope mechanism. Its runtime impact is limited to preview focus and composer expansion behavior, with no schema, security, billing, infrastructure, or default changes.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge merged commit c0ebc88 into mainSep 4, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/image-preview-return-focus branch September 4, 2026 00:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge