authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-09 11:17:43-07:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-09 11:57:27-07:00
loge354e8697f490c729d7022b28e921f4c12e0a8ae
tree4a12f1dedeff739a06bc6843eb85ffe5a0eb09a0
parent993b5785121765a56628bfd4852591f672b743af

Prevent compact diff sidebar from breaking chat composer layout

- Add composer footer/action data attributes for targeted width measurement - Enforce a minimum composer width before allowing inline diff sidebar

2 files changed, 19 insertions(+), 2 deletions(-)

apps/web/src/components/ChatView.tsx+2-1
...@@ -3707,6 +3707,7 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -3707,6 +3707,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
3707 </div>3707 </div>
3708 ) : (3708 ) : (
3709 <div3709 <div
3710 data-chat-composer-footer="true"
3710 className={cn(3711 className={cn(
3711 "flex items-center justify-between px-2.5 pb-2.5 sm:px-3 sm:pb-3",3712 "flex items-center justify-between px-2.5 pb-2.5 sm:px-3 sm:pb-3",
3712 isComposerFooterCompact ? "gap-1.5" : "flex-wrap gap-2 sm:flex-nowrap sm:gap-0",3713 isComposerFooterCompact ? "gap-1.5" : "flex-wrap gap-2 sm:flex-nowrap sm:gap-0",
...@@ -3835,7 +3836,7 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -3835,7 +3836,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
3835 </div>3836 </div>
38363837
3837 {/* Right side: send / stop button */}3838 {/* Right side: send / stop button */}
3838 <div className="flex shrink-0 items-center gap-2">3839 <div data-chat-composer-actions="right" className="flex shrink-0 items-center gap-2">
3839 {isPreparingWorktree ? (3840 {isPreparingWorktree ? (
3840 <span className="text-muted-foreground/70 text-xs">Preparing worktree...</span>3841 <span className="text-muted-foreground/70 text-xs">Preparing worktree...</span>
3841 ) : null}3842 ) : null}
apps/web/src/routes/_chat.$threadId.tsx+17-1
...@@ -15,6 +15,7 @@ const DIFF_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 1180px)";...@@ -15,6 +15,7 @@ const DIFF_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 1180px)";
15const DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY = "chat_diff_sidebar_width";15const DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY = "chat_diff_sidebar_width";
16const DIFF_INLINE_DEFAULT_WIDTH = "clamp(28rem,48vw,44rem)";16const DIFF_INLINE_DEFAULT_WIDTH = "clamp(28rem,48vw,44rem)";
17const DIFF_INLINE_SIDEBAR_MIN_WIDTH = 26 * 16;17const DIFF_INLINE_SIDEBAR_MIN_WIDTH = 26 * 16;
18const COMPOSER_COMPACT_MIN_LEFT_CONTROLS_WIDTH_PX = 208;
1819
19const DiffPanelSheet = (props: {20const DiffPanelSheet = (props: {
20 children: ReactNode;21 children: ReactNode;
...@@ -91,8 +92,23 @@ const DiffPanelInlineSidebar = (props: {...@@ -91,8 +92,23 @@ const DiffPanelInlineSidebar = (props: {
91 composerViewport.clientWidth - viewportPaddingLeft - viewportPaddingRight,92 composerViewport.clientWidth - viewportPaddingLeft - viewportPaddingRight,
92 );93 );
93 const formRect = composerForm.getBoundingClientRect();94 const formRect = composerForm.getBoundingClientRect();
95 const composerFooter = composerForm.querySelector<HTMLElement>("[data-chat-composer-footer='true']");
96 const composerRightActions = composerForm.querySelector<HTMLElement>(
97 "[data-chat-composer-actions='right']",
98 );
99 const composerRightActionsWidth = composerRightActions?.getBoundingClientRect().width ?? 0;
100 const composerFooterGap = composerFooter
101 ? Number.parseFloat(window.getComputedStyle(composerFooter).columnGap) ||
102 Number.parseFloat(window.getComputedStyle(composerFooter).gap) ||
103 0
104 : 0;
105 const minimumComposerWidth =
106 COMPOSER_COMPACT_MIN_LEFT_CONTROLS_WIDTH_PX +
107 composerRightActionsWidth +
108 composerFooterGap;
94 const hasComposerOverflow = composerForm.scrollWidth > composerForm.clientWidth + 0.5;109 const hasComposerOverflow = composerForm.scrollWidth > composerForm.clientWidth + 0.5;
95 const overflowsViewport = formRect.width > viewportContentWidth + 0.5;110 const overflowsViewport = formRect.width > viewportContentWidth + 0.5;
111 const violatesMinimumComposerWidth = composerForm.clientWidth + 0.5 < minimumComposerWidth;
96112
97 if (previousSidebarWidth.length > 0) {113 if (previousSidebarWidth.length > 0) {
98 wrapper.style.setProperty("--sidebar-width", previousSidebarWidth);114 wrapper.style.setProperty("--sidebar-width", previousSidebarWidth);
...@@ -100,7 +116,7 @@ const DiffPanelInlineSidebar = (props: {...@@ -100,7 +116,7 @@ const DiffPanelInlineSidebar = (props: {
100 wrapper.style.removeProperty("--sidebar-width");116 wrapper.style.removeProperty("--sidebar-width");
101 }117 }
102118
103 return !hasComposerOverflow && !overflowsViewport;119 return !hasComposerOverflow && !overflowsViewport && !violatesMinimumComposerWidth;
104 },120 },
105 [],121 [],
106 );122 );