Skip to content

Commit e01d6ae

Browse files
chore: improve mobile hover states (#699)
* feat: integrate inline question UI into prompt box for mobile * feat: improve mobile UX for Q&A and git panel interactions * feat: close mobile sidebar on session creation --------- Co-authored-by: willsather <56037657+willsather@users.noreply.github.com>
1 parent bd3e5c2 commit e01d6ae

File tree

6 files changed

+104
-494
lines changed

6 files changed

+104
-494
lines changed

apps/web/app/sessions/[sessionId]/chats/[chatId]/chat-tabs.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
TooltipTrigger,
2727
} from "@/components/ui/tooltip";
2828
import { cn } from "@/lib/utils";
29+
import { useIsMobile } from "@/hooks/use-mobile";
2930
import { useGitPanel } from "./git-panel-context";
3031

3132
type ChatTabsProps = {
@@ -51,6 +52,8 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
5152
setFileTabDismissed,
5253
} = useGitPanel();
5354

55+
const isMobile = useIsMobile();
56+
5457
const [renamingChatId, setRenamingChatId] = useState<string | null>(null);
5558
const [renameValue, setRenameValue] = useState("");
5659
const [deletingChatId, setDeletingChatId] = useState<string | null>(null);
@@ -226,7 +229,10 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
226229
<button
227230
type="button"
228231
onClick={handleCloseChanges}
229-
className="mr-1 rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100"
232+
className={cn(
233+
"mr-1 rounded p-0.5 text-muted-foreground transition-opacity hover:bg-accent hover:text-foreground",
234+
isMobile ? "opacity-100" : "opacity-0 group-hover:opacity-100",
235+
)}
230236
>
231237
<X className="h-3 w-3" />
232238
</button>
@@ -254,7 +260,10 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
254260
<button
255261
type="button"
256262
onClick={handleCloseFile}
257-
className="mr-1 rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100"
263+
className={cn(
264+
"mr-1 rounded p-0.5 text-muted-foreground transition-opacity hover:bg-accent hover:text-foreground",
265+
isMobile ? "opacity-100" : "opacity-0 group-hover:opacity-100",
266+
)}
258267
>
259268
<X className="h-3 w-3" />
260269
</button>
@@ -326,7 +335,12 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
326335
)}
327336

328337
{!isRenaming && (
329-
<div className="flex items-center gap-0.5 pr-1 opacity-0 transition-opacity group-hover:opacity-100">
338+
<div
339+
className={cn(
340+
"flex items-center gap-0.5 pr-1 transition-opacity",
341+
isMobile ? "opacity-100" : "opacity-0 group-hover:opacity-100",
342+
)}
343+
>
330344
<button
331345
type="button"
332346
onClick={(e) => {
@@ -378,6 +392,7 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
378392
handleFinishRename,
379393
handleStartRename,
380394
insertAt,
395+
isMobile,
381396
prefetchChat,
382397
renameValue,
383398
renamingChatId,

apps/web/app/sessions/[sessionId]/chats/[chatId]/git-panel-context.tsx

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
type ReactNode,
1111
type RefObject,
1212
} from "react";
13+
import { useIsMobile } from "@/hooks/use-mobile";
1314

1415
export type GitPanelTab = "diff" | "pr" | "files";
1516
export type ActiveView = "chat" | "diff" | "file";
@@ -83,6 +84,7 @@ const GitPanelContext = createContext<GitPanelContextValue | undefined>(
8384
);
8485

8586
export function GitPanelProvider({ children }: { children: ReactNode }) {
87+
const isMobile = useIsMobile();
8688
const [gitPanelOpen, setGitPanelOpen] = useState(false);
8789
const [gitPanelTab, setGitPanelTab] = useState<GitPanelTab>("files");
8890
const [activeView, setActiveView] = useState<ActiveView>("chat");
@@ -99,18 +101,26 @@ export function GitPanelProvider({ children }: { children: ReactNode }) {
99101
const panelPortalRef = useRef<HTMLDivElement | null>(null);
100102
const headerActionsRef = useRef<HTMLDivElement | null>(null);
101103

102-
const openDiffToFile = useCallback((filePath: string) => {
103-
setFocusedDiffFile(filePath);
104-
setFocusedDiffRequestId((prev) => prev + 1);
105-
setActiveView("diff");
106-
setChangesTabDismissed(false);
107-
}, []);
108-
109-
const openFileTab = useCallback((filePath: string) => {
110-
setFocusedFilePath(filePath);
111-
setActiveView("file");
112-
setFileTabDismissed(false);
113-
}, []);
104+
const openDiffToFile = useCallback(
105+
(filePath: string) => {
106+
setFocusedDiffFile(filePath);
107+
setFocusedDiffRequestId((prev) => prev + 1);
108+
setActiveView("diff");
109+
setChangesTabDismissed(false);
110+
if (isMobile) setGitPanelOpen(false);
111+
},
112+
[isMobile],
113+
);
114+
115+
const openFileTab = useCallback(
116+
(filePath: string) => {
117+
setFocusedFilePath(filePath);
118+
setActiveView("file");
119+
setFileTabDismissed(false);
120+
if (isMobile) setGitPanelOpen(false);
121+
},
122+
[isMobile],
123+
);
114124

115125
const value = useMemo(
116126
() => ({

apps/web/app/sessions/[sessionId]/chats/[chatId]/session-chat-content.tsx

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import { FileSuggestionsDropdown } from "@/components/file-suggestions-dropdown"
6363
import { ImageAttachmentsPreview } from "@/components/image-attachments-preview";
6464
import { TextAttachmentsPreview } from "@/components/text-attachments-preview";
6565
import { ModelSelectorCompact } from "@/components/model-selector-compact";
66-
import { QuestionPanel } from "@/components/question-panel";
66+
import { useInlineQuestion } from "@/components/inline-question-input";
6767
import { SlashCommandDropdown } from "@/components/slash-command-dropdown";
6868
import { SnippetChip } from "@/components/snippet-chip";
6969
import { AssistantMessageGroups } from "@/components/assistant-message-groups";
@@ -2638,6 +2638,26 @@ export function SessionChatContent({
26382638
}
26392639
}, [questionToolCallId, addToolOutput]);
26402640

2641+
// Stable empty array so the hook doesn't reset on every render when there's no question
2642+
const emptyQuestions = useMemo(
2643+
() => [] as AskUserQuestionInput["questions"],
2644+
[],
2645+
);
2646+
2647+
const inlineQuestion = useInlineQuestion({
2648+
questions:
2649+
hasPendingQuestion && pendingQuestionPart
2650+
? pendingQuestionPart.input.questions
2651+
: emptyQuestions,
2652+
onSubmit: handleQuestionSubmit,
2653+
onCancel: handleQuestionCancel,
2654+
textareaValue: input,
2655+
onTextareaChange: setInput,
2656+
});
2657+
2658+
// Inline question UI is integrated into the prompt box on all viewports
2659+
const showInlineQuestion = inlineQuestion.isActive;
2660+
26412661
const isReconnectingSandbox =
26422662
reconnectionStatus === "checking" &&
26432663
!sandboxInfo &&
@@ -3656,15 +3676,6 @@ export function SessionChatContent({
36563676
)}
36573677
</div>
36583678

3659-
{/* Question Panel */}
3660-
{hasPendingQuestion && pendingQuestionPart && (
3661-
<QuestionPanel
3662-
questions={pendingQuestionPart.input.questions}
3663-
onSubmit={handleQuestionSubmit}
3664-
onCancel={handleQuestionCancel}
3665-
/>
3666-
)}
3667-
36683679
{/* Input */}
36693680
<div className="p-4 pb-2 sm:pb-8">
36703681
<div className="mx-auto max-w-4xl space-y-2">
@@ -3755,6 +3766,8 @@ export function SessionChatContent({
37553766
<form
37563767
onSubmit={async (e) => {
37573768
e.preventDefault();
3769+
// When inline question is active, don't send a chat message
3770+
if (showInlineQuestion) return;
37583771
if (
37593772
isArchived ||
37603773
!isSandboxActive ||
@@ -3960,12 +3973,19 @@ export function SessionChatContent({
39603973
</div>
39613974
)}
39623975

3976+
{/* Inline question UI for mobile — rendered inside prompt box */}
3977+
{showInlineQuestion && inlineQuestion.questionHeaderUI}
3978+
39633979
{/* Textarea area */}
39643980
<div className="px-4 pb-2 pt-3">
39653981
<textarea
39663982
ref={inputRef}
39673983
value={input}
3968-
placeholder="Request changes or ask a question..."
3984+
placeholder={
3985+
showInlineQuestion
3986+
? inlineQuestion.placeholder
3987+
: "Request changes or ask a question..."
3988+
}
39693989
rows={1}
39703990
onFocus={handleTextareaFocus}
39713991
onChange={(e) => {
@@ -3975,6 +3995,16 @@ export function SessionChatContent({
39753995
);
39763996
}}
39773997
onKeyDown={(e) => {
3998+
// When inline question is active, Enter advances the question
3999+
if (
4000+
showInlineQuestion &&
4001+
e.key === "Enter" &&
4002+
!e.shiftKey
4003+
) {
4004+
e.preventDefault();
4005+
inlineQuestion.handleNext();
4006+
return;
4007+
}
39784008
// Let suggestions handle keyboard events first
39794009
if (handleSuggestionsKeyDown(e)) {
39804010
return;
@@ -4144,7 +4174,21 @@ export function SessionChatContent({
41444174
)}
41454175
</Button>
41464176

4147-
{isChatInFlight || hasPendingResponse ? (
4177+
{showInlineQuestion ? (
4178+
<Button
4179+
type="button"
4180+
size="sm"
4181+
onClick={(e) => {
4182+
e.preventDefault();
4183+
inlineQuestion.handleNext();
4184+
}}
4185+
disabled={!inlineQuestion.hasCurrentAnswer}
4186+
className="h-8 rounded-full bg-primary px-3 text-xs text-primary-foreground hover:bg-primary/90 disabled:opacity-30"
4187+
>
4188+
<Check className="mr-1 h-3 w-3" />
4189+
{inlineQuestion.buttonLabel}
4190+
</Button>
4191+
) : isChatInFlight || hasPendingResponse ? (
41484192
<Button
41494193
type="button"
41504194
size="icon"

apps/web/components/inbox-sidebar.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -863,9 +863,10 @@ export function InboxSidebar({
863863

864864
const handleCreateForRepo = useCallback(
865865
(owner: string, repo: string) => {
866+
if (isMobile) setOpenMobile(false);
866867
onCreateSessionForRepo(owner, repo);
867868
},
868-
[onCreateSessionForRepo],
869+
[isMobile, setOpenMobile, onCreateSessionForRepo],
869870
);
870871

871872
const handleOpenBranchPicker = useCallback((owner: string, repo: string) => {
@@ -883,13 +884,14 @@ export function InboxSidebar({
883884
branch,
884885
);
885886
setBranchPickerRepo(null);
887+
if (isMobile) setOpenMobile(false);
886888
} catch (error) {
887889
console.error("Failed to create session from branch:", error);
888890
} finally {
889891
setIsCreatingFromBranch(false);
890892
}
891893
},
892-
[branchPickerRepo, onCreateSessionFromBranch],
894+
[branchPickerRepo, onCreateSessionFromBranch, isMobile, setOpenMobile],
893895
);
894896

895897
return (
@@ -1022,7 +1024,9 @@ export function InboxSidebar({
10221024
</span>
10231025
</button>
10241026
{hasRepo ? (
1025-
<span className="hidden shrink-0 items-center gap-0.5 group-hover/repo:flex">
1027+
<span
1028+
className={`shrink-0 items-center gap-0.5 ${isMobile ? "flex" : "hidden group-hover/repo:flex"}`}
1029+
>
10261030
<Tooltip>
10271031
<TooltipTrigger asChild>
10281032
<button

0 commit comments

Comments
 (0)