From d678a4244ed7bd92a6973820fcfacb5e8ecc63a7 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 24 Aug 2026 01:44:15 +0300 Subject: [PATCH] fix(mobile): knowledge panel tabs, autocomplete height floor, status row gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The project knowledge panel's section sidebar becomes a horizontal pill strip on mobile, where a half-width side column left no room for content. The autocomplete popups get their 120px minimum height back — browser keyboard panning could put the anchor above the measured boundary and collapse them to nothing. The status row's 24px footer-swap reservation now applies only to the assistant-status instance; the accessory-only row above the composer (pending changes) takes the normal 8px instead of floating a stray gap. --- packages/ui/src/components/chat/StatusRow.tsx | 5 ++- .../chat/useMobileAutocompleteMaxHeight.ts | 6 ++- .../project-context/ProjectNotesTodoPanel.tsx | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/chat/StatusRow.tsx b/packages/ui/src/components/chat/StatusRow.tsx index 1159e822..83b4b52b 100644 --- a/packages/ui/src/components/chat/StatusRow.tsx +++ b/packages/ui/src/components/chat/StatusRow.tsx @@ -313,7 +313,10 @@ export const StatusRow: React.FC = ({ // carries ~12px more structure BELOW its footer than this row has — so // the swap used to lift the line up. mb-6 (24px) reserves that space // under this row instead (verified: row top 636 == footer top 636). - className={cn("mb-6", !hasLeftAccessory && "chat-column")} + // The reservation belongs to the assistant-status swap only: a row that + // renders just an accessory (the pending-changes bar) takes the normal + // 8px, or it floats a stray gap above the composer. + className={cn(showAssistantStatus ? "mb-6" : "mb-2", !hasLeftAccessory && "chat-column")} style={STATUS_ROW_CONTAINER_STYLE} > {/* h-8 matches the turn footer's real row height: its h-8 action diff --git a/packages/ui/src/components/chat/useMobileAutocompleteMaxHeight.ts b/packages/ui/src/components/chat/useMobileAutocompleteMaxHeight.ts index 31e36129..64356f6f 100644 --- a/packages/ui/src/components/chat/useMobileAutocompleteMaxHeight.ts +++ b/packages/ui/src/components/chat/useMobileAutocompleteMaxHeight.ts @@ -35,7 +35,11 @@ export const useMobileAutocompleteMaxHeight = ( // The popup's bottom edge is its anchor (composer top) and does not // depend on its current height. const available = Math.max(0, Math.floor(el.getBoundingClientRect().bottom - boundaryTop - 8)); - const next = available < normalMaxHeight ? available : undefined; + // Floor: browser keyboard panning can put the anchor above the + // measured boundary for a frame (or for the whole pan), which + // would collapse the popup to zero height. A short popup that + // slightly overlaps the header beats an invisible one. + const next = available < normalMaxHeight ? Math.max(120, available) : undefined; setMaxHeight((prev) => (prev === next ? prev : next)); }; measure(); diff --git a/packages/ui/src/components/session/project-context/ProjectNotesTodoPanel.tsx b/packages/ui/src/components/session/project-context/ProjectNotesTodoPanel.tsx index e25a2bfa..e26b70ca 100644 --- a/packages/ui/src/components/session/project-context/ProjectNotesTodoPanel.tsx +++ b/packages/ui/src/components/session/project-context/ProjectNotesTodoPanel.tsx @@ -135,6 +135,7 @@ export const ProjectNotesTodoPanel: React.FC = ({ const globalMemory = useAgentMemoryStore((state) => state.global); const projectMemory = useAgentMemoryStore((state) => state.project); + const isMobile = useUIStore((state) => state.isMobile); const storedTab = useUIStore((state) => state.projectContextTab); const setStoredTab = useUIStore((state) => state.setProjectContextTab); const requestedTab = TAB_ORDER.includes(storedTab as ProjectContextTab) @@ -413,6 +414,42 @@ export const ProjectNotesTodoPanel: React.FC = ({ + {/* Mobile: a half-width panel has no room for a side column, so the + sections become the same pill strip the mobile drawer's surface + tabs use — the active pill carries the label, the rest collapse to + icon and count. */} + {isMobile ? ( + + ) : null} + {/* Content first, sidebar on the right — the same order and the same drag-to-resize edge the files surface uses, so the two panels do not disagree about where navigation lives. */} @@ -472,6 +509,7 @@ export const ProjectNotesTodoPanel: React.FC = ({ ) : null} + {isMobile ? null : ( + )}