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) {
37073707 </div>
37083708 ) : (
37093709 <div
3710 data-chat-composer-footer="true"
37103711 className={cn(
37113712 "flex items-center justify-between px-2.5 pb-2.5 sm:px-3 sm:pb-3",
37123713 isComposerFooterCompact ? "gap-1.5" : "flex-wrap gap-2 sm:flex-nowrap sm:gap-0",
......@@ -3835,7 +3836,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
38353836 </div>
38363837
38373838 {/* 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">
38393840 {isPreparingWorktree ? (
38403841 <span className="text-muted-foreground/70 text-xs">Preparing worktree...</span>
38413842 ) : null}
apps/web/src/routes/_chat.$threadId.tsx+17-1
......@@ -15,6 +15,7 @@ const DIFF_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 1180px)";
1515const DIFF_INLINE_SIDEBAR_WIDTH_STORAGE_KEY = "chat_diff_sidebar_width";
1616const DIFF_INLINE_DEFAULT_WIDTH = "clamp(28rem,48vw,44rem)";
1717const DIFF_INLINE_SIDEBAR_MIN_WIDTH = 26 * 16;
18const COMPOSER_COMPACT_MIN_LEFT_CONTROLS_WIDTH_PX = 208;
1819
1920const DiffPanelSheet = (props: {
2021 children: ReactNode;
......@@ -91,8 +92,23 @@ const DiffPanelInlineSidebar = (props: {
9192 composerViewport.clientWidth - viewportPaddingLeft - viewportPaddingRight,
9293 );
9394 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;
94109 const hasComposerOverflow = composerForm.scrollWidth > composerForm.clientWidth + 0.5;
95110 const overflowsViewport = formRect.width > viewportContentWidth + 0.5;
111 const violatesMinimumComposerWidth = composerForm.clientWidth + 0.5 < minimumComposerWidth;
96112
97113 if (previousSidebarWidth.length > 0) {
98114 wrapper.style.setProperty("--sidebar-width", previousSidebarWidth);
......@@ -100,7 +116,7 @@ const DiffPanelInlineSidebar = (props: {
100116 wrapper.style.removeProperty("--sidebar-width");
101117 }
102118
103 return !hasComposerOverflow && !overflowsViewport;
119 return !hasComposerOverflow && !overflowsViewport && !violatesMinimumComposerWidth;
104120 },
105121 [],
106122 );