| author | |
| committer | |
| log | d649ccf71598a38aac6ea0e7f2f9c034b1bc78b1 |
| tree | bb27d77bee4c461133a35b0f29ce62bdbae41407 |
| parent | f54f4385e518504f87001afef51bdf3b0be2a5be |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>13 files changed, 277 insertions(+), 65 deletions(-)
apps/web/index.html+4-1| ... | ... | @@ -2,7 +2,10 @@ |
| 2 | 2 | <html lang="en"> |
| 3 | 3 | <head> |
| 4 | 4 | <meta charset="UTF-8" /> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> | |
| 5 | <meta | |
| 6 | name="viewport" | |
| 7 | content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" | |
| 8 | /> | |
| 6 | 9 | <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" /> |
| 7 | 10 | <meta name="theme-color" content="#161616" media="(prefers-color-scheme: dark)" /> |
| 8 | 11 | <meta name="theme-color" content="#161616" /> |
apps/web/src/components/AppSidebarLayout.tsx+1-1| ... | ... | @@ -54,7 +54,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { |
| 54 | 54 | }, [navigate]); |
| 55 | 55 | |
| 56 | 56 | return ( |
| 57 | <SidebarProvider defaultOpen> | |
| 57 | <SidebarProvider className="h-dvh! min-h-0!" defaultOpen> | |
| 58 | 58 | <Sidebar |
| 59 | 59 | side="left" |
| 60 | 60 | collapsible="offcanvas" |
apps/web/src/components/BranchToolbar.tsx+178-18| ... | ... | @@ -1,18 +1,41 @@ |
| 1 | 1 | import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime"; |
| 2 | 2 | import type { EnvironmentId, ThreadId } from "@t3tools/contracts"; |
| 3 | import { | |
| 4 | ChevronDownIcon, | |
| 5 | CloudIcon, | |
| 6 | FolderGit2Icon, | |
| 7 | FolderGitIcon, | |
| 8 | FolderIcon, | |
| 9 | MonitorIcon, | |
| 10 | } from "lucide-react"; | |
| 3 | 11 | import { memo, useMemo } from "react"; |
| 4 | 12 | |
| 5 | 13 | import { useComposerDraftStore, type DraftId } from "../composerDraftStore"; |
| 14 | import { useIsMobile } from "../hooks/useMediaQuery"; | |
| 6 | 15 | import { useStore } from "../store"; |
| 7 | 16 | import { createProjectSelectorByRef, createThreadSelectorByRef } from "../storeSelectors"; |
| 8 | 17 | import { |
| 9 | 18 | type EnvMode, |
| 10 | 19 | type EnvironmentOption, |
| 20 | resolveCurrentWorkspaceLabel, | |
| 21 | resolveEnvModeLabel, | |
| 11 | 22 | resolveEffectiveEnvMode, |
| 23 | resolveLockedWorkspaceLabel, | |
| 12 | 24 | } from "./BranchToolbar.logic"; |
| 13 | 25 | import { BranchToolbarBranchSelector } from "./BranchToolbarBranchSelector"; |
| 14 | 26 | import { BranchToolbarEnvironmentSelector } from "./BranchToolbarEnvironmentSelector"; |
| 15 | 27 | import { BranchToolbarEnvModeSelector } from "./BranchToolbarEnvModeSelector"; |
| 28 | import { Button } from "./ui/button"; | |
| 29 | import { | |
| 30 | Menu, | |
| 31 | MenuGroup, | |
| 32 | MenuGroupLabel, | |
| 33 | MenuPopup, | |
| 34 | MenuRadioGroup, | |
| 35 | MenuRadioItem, | |
| 36 | MenuSeparator, | |
| 37 | MenuTrigger, | |
| 38 | } from "./ui/menu"; | |
| 16 | 39 | import { Separator } from "./ui/separator"; |
| 17 | 40 | |
| 18 | 41 | interface BranchToolbarProps { |
| ... | ... | @@ -30,6 +53,126 @@ interface BranchToolbarProps { |
| 30 | 53 | onEnvironmentChange?: (environmentId: EnvironmentId) => void; |
| 31 | 54 | } |
| 32 | 55 | |
| 56 | interface MobileRunContextSelectorProps { | |
| 57 | envLocked: boolean; | |
| 58 | envModeLocked: boolean; | |
| 59 | environmentId: EnvironmentId; | |
| 60 | availableEnvironments: readonly EnvironmentOption[] | undefined; | |
| 61 | showEnvironmentPicker: boolean; | |
| 62 | onEnvironmentChange: ((environmentId: EnvironmentId) => void) | undefined; | |
| 63 | effectiveEnvMode: EnvMode; | |
| 64 | activeWorktreePath: string | null; | |
| 65 | onEnvModeChange: (mode: EnvMode) => void; | |
| 66 | } | |
| 67 | ||
| 68 | const MobileRunContextSelector = memo(function MobileRunContextSelector({ | |
| 69 | envLocked, | |
| 70 | envModeLocked, | |
| 71 | environmentId, | |
| 72 | availableEnvironments, | |
| 73 | showEnvironmentPicker, | |
| 74 | onEnvironmentChange, | |
| 75 | effectiveEnvMode, | |
| 76 | activeWorktreePath, | |
| 77 | onEnvModeChange, | |
| 78 | }: MobileRunContextSelectorProps) { | |
| 79 | const activeEnvironment = useMemo( | |
| 80 | () => availableEnvironments?.find((env) => env.environmentId === environmentId) ?? null, | |
| 81 | [availableEnvironments, environmentId], | |
| 82 | ); | |
| 83 | const environmentLabel = activeEnvironment?.label ?? "Run on"; | |
| 84 | const EnvironmentIcon = activeEnvironment?.isPrimary ? MonitorIcon : CloudIcon; | |
| 85 | const WorkspaceIcon = | |
| 86 | effectiveEnvMode === "worktree" | |
| 87 | ? FolderGit2Icon | |
| 88 | : activeWorktreePath | |
| 89 | ? FolderGitIcon | |
| 90 | : FolderIcon; | |
| 91 | const workspaceLabel = envModeLocked | |
| 92 | ? resolveLockedWorkspaceLabel(activeWorktreePath) | |
| 93 | : effectiveEnvMode === "worktree" | |
| 94 | ? resolveEnvModeLabel("worktree") | |
| 95 | : resolveCurrentWorkspaceLabel(activeWorktreePath); | |
| 96 | ||
| 97 | return ( | |
| 98 | <Menu> | |
| 99 | <MenuTrigger | |
| 100 | render={<Button variant="ghost" size="xs" />} | |
| 101 | className="min-w-0 max-w-[48%] flex-1 justify-start text-muted-foreground/70 hover:text-foreground/80 md:hidden" | |
| 102 | > | |
| 103 | {showEnvironmentPicker ? ( | |
| 104 | <> | |
| 105 | <EnvironmentIcon className="size-3 shrink-0" /> | |
| 106 | <span className="min-w-0 truncate">{environmentLabel}</span> | |
| 107 | </> | |
| 108 | ) : ( | |
| 109 | <> | |
| 110 | <WorkspaceIcon className="size-3 shrink-0" /> | |
| 111 | <span className="min-w-0 truncate">{workspaceLabel}</span> | |
| 112 | </> | |
| 113 | )} | |
| 114 | <ChevronDownIcon className="size-3 shrink-0 opacity-50" /> | |
| 115 | </MenuTrigger> | |
| 116 | <MenuPopup align="start" side="top" className="w-64"> | |
| 117 | {showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? ( | |
| 118 | <> | |
| 119 | <MenuGroup> | |
| 120 | <MenuGroupLabel>Run on</MenuGroupLabel> | |
| 121 | <MenuRadioGroup | |
| 122 | value={environmentId} | |
| 123 | onValueChange={(value) => onEnvironmentChange(value as EnvironmentId)} | |
| 124 | > | |
| 125 | {availableEnvironments.map((env) => { | |
| 126 | const Icon = env.isPrimary ? MonitorIcon : CloudIcon; | |
| 127 | return ( | |
| 128 | <MenuRadioItem | |
| 129 | key={env.environmentId} | |
| 130 | disabled={envLocked} | |
| 131 | value={env.environmentId} | |
| 132 | > | |
| 133 | <span className="flex min-w-0 items-center gap-1.5"> | |
| 134 | <Icon className="size-3" /> | |
| 135 | <span className="min-w-0 truncate">{env.label}</span> | |
| 136 | </span> | |
| 137 | </MenuRadioItem> | |
| 138 | ); | |
| 139 | })} | |
| 140 | </MenuRadioGroup> | |
| 141 | </MenuGroup> | |
| 142 | <MenuSeparator /> | |
| 143 | </> | |
| 144 | ) : null} | |
| 145 | <MenuGroup> | |
| 146 | <MenuGroupLabel>Workspace</MenuGroupLabel> | |
| 147 | <MenuRadioGroup | |
| 148 | value={effectiveEnvMode} | |
| 149 | onValueChange={(value) => onEnvModeChange(value as EnvMode)} | |
| 150 | > | |
| 151 | <MenuRadioItem disabled={envModeLocked} value="local"> | |
| 152 | <span className="flex min-w-0 items-center gap-1.5"> | |
| 153 | {activeWorktreePath ? ( | |
| 154 | <FolderGitIcon className="size-3" /> | |
| 155 | ) : ( | |
| 156 | <FolderIcon className="size-3" /> | |
| 157 | )} | |
| 158 | <span className="min-w-0 truncate"> | |
| 159 | {resolveCurrentWorkspaceLabel(activeWorktreePath)} | |
| 160 | </span> | |
| 161 | </span> | |
| 162 | </MenuRadioItem> | |
| 163 | <MenuRadioItem disabled={envModeLocked} value="worktree"> | |
| 164 | <span className="flex min-w-0 items-center gap-1.5"> | |
| 165 | <FolderGit2Icon className="size-3" /> | |
| 166 | <span className="min-w-0 truncate">{resolveEnvModeLabel("worktree")}</span> | |
| 167 | </span> | |
| 168 | </MenuRadioItem> | |
| 169 | </MenuRadioGroup> | |
| 170 | </MenuGroup> | |
| 171 | </MenuPopup> | |
| 172 | </Menu> | |
| 173 | ); | |
| 174 | }); | |
| 175 | ||
| 33 | 176 | export const BranchToolbar = memo(function BranchToolbar({ |
| 34 | 177 | environmentId, |
| 35 | 178 | threadId, |
| ... | ... | @@ -74,34 +217,51 @@ export const BranchToolbar = memo(function BranchToolbar({ |
| 74 | 217 | }); |
| 75 | 218 | const envModeLocked = envLocked || (serverThread !== undefined && activeWorktreePath !== null); |
| 76 | 219 | |
| 77 | const showEnvironmentPicker = | |
| 78 | availableEnvironments && availableEnvironments.length > 1 && onEnvironmentChange; | |
| 220 | const showEnvironmentPicker = Boolean( | |
| 221 | availableEnvironments && availableEnvironments.length > 1 && onEnvironmentChange, | |
| 222 | ); | |
| 223 | const isMobile = useIsMobile(); | |
| 79 | 224 | |
| 80 | 225 | if (!hasActiveThread || !activeProject) return null; |
| 81 | 226 | |
| 82 | 227 | return ( |
| 83 | <div className="mx-auto flex w-full max-w-208 items-center justify-between px-2.5 pb-3 pt-1 sm:px-3"> | |
| 84 | <div className="flex items-center gap-1"> | |
| 85 | {showEnvironmentPicker && ( | |
| 86 | <> | |
| 87 | <BranchToolbarEnvironmentSelector | |
| 88 | envLocked={envLocked} | |
| 89 | environmentId={environmentId} | |
| 90 | availableEnvironments={availableEnvironments} | |
| 91 | onEnvironmentChange={onEnvironmentChange} | |
| 92 | /> | |
| 93 | <Separator orientation="vertical" className="mx-0.5 h-3.5!" /> | |
| 94 | </> | |
| 95 | )} | |
| 96 | <BranchToolbarEnvModeSelector | |
| 97 | envLocked={envModeLocked} | |
| 228 | <div className="mx-auto flex w-full max-w-208 items-center gap-2 px-2.5 pb-3 pt-1 sm:px-3"> | |
| 229 | {isMobile ? ( | |
| 230 | <MobileRunContextSelector | |
| 231 | envLocked={envLocked} | |
| 232 | envModeLocked={envModeLocked} | |
| 233 | environmentId={environmentId} | |
| 234 | availableEnvironments={availableEnvironments} | |
| 235 | showEnvironmentPicker={showEnvironmentPicker} | |
| 236 | onEnvironmentChange={onEnvironmentChange} | |
| 98 | 237 | effectiveEnvMode={effectiveEnvMode} |
| 99 | 238 | activeWorktreePath={activeWorktreePath} |
| 100 | 239 | onEnvModeChange={onEnvModeChange} |
| 101 | 240 | /> |
| 102 | </div> | |
| 241 | ) : ( | |
| 242 | <div className="flex min-w-0 shrink-0 items-center gap-1"> | |
| 243 | {showEnvironmentPicker && availableEnvironments && onEnvironmentChange && ( | |
| 244 | <> | |
| 245 | <BranchToolbarEnvironmentSelector | |
| 246 | envLocked={envLocked} | |
| 247 | environmentId={environmentId} | |
| 248 | availableEnvironments={availableEnvironments} | |
| 249 | onEnvironmentChange={onEnvironmentChange} | |
| 250 | /> | |
| 251 | <Separator orientation="vertical" className="mx-0.5 h-3.5!" /> | |
| 252 | </> | |
| 253 | )} | |
| 254 | <BranchToolbarEnvModeSelector | |
| 255 | envLocked={envModeLocked} | |
| 256 | effectiveEnvMode={effectiveEnvMode} | |
| 257 | activeWorktreePath={activeWorktreePath} | |
| 258 | onEnvModeChange={onEnvModeChange} | |
| 259 | /> | |
| 260 | </div> | |
| 261 | )} | |
| 103 | 262 | |
| 104 | 263 | <BranchToolbarBranchSelector |
| 264 | className="min-w-0 flex-1 justify-end md:ml-auto md:flex-none" | |
| 105 | 265 | environmentId={environmentId} |
| 106 | 266 | threadId={threadId} |
| 107 | 267 | {...(draftId ? { draftId } : {})} |
apps/web/src/components/BranchToolbarBranchSelector.tsx+6-3| ... | ... | @@ -19,6 +19,7 @@ import { readEnvironmentApi } from "../environmentApi"; |
| 19 | 19 | import { gitBranchSearchInfiniteQueryOptions, gitQueryKeys } from "../lib/gitReactQuery"; |
| 20 | 20 | import { useGitStatus } from "../lib/gitStatusState"; |
| 21 | 21 | import { newCommandId } from "../lib/utils"; |
| 22 | import { cn } from "../lib/utils"; | |
| 22 | 23 | import { parsePullRequestReference } from "../pullRequestReference"; |
| 23 | 24 | import { useStore } from "../store"; |
| 24 | 25 | import { createProjectSelectorByRef, createThreadSelectorByRef } from "../storeSelectors"; |
| ... | ... | @@ -45,6 +46,7 @@ import { |
| 45 | 46 | import { stackedThreadToast, toastManager } from "./ui/toast"; |
| 46 | 47 | |
| 47 | 48 | interface BranchToolbarBranchSelectorProps { |
| 49 | className?: string; | |
| 48 | 50 | environmentId: EnvironmentId; |
| 49 | 51 | threadId: ThreadId; |
| 50 | 52 | draftId?: DraftId; |
| ... | ... | @@ -76,6 +78,7 @@ function getBranchTriggerLabel(input: { |
| 76 | 78 | } |
| 77 | 79 | |
| 78 | 80 | export function BranchToolbarBranchSelector({ |
| 81 | className, | |
| 79 | 82 | environmentId, |
| 80 | 83 | threadId, |
| 81 | 84 | draftId, |
| ... | ... | @@ -577,11 +580,11 @@ export function BranchToolbarBranchSelector({ |
| 577 | 580 | > |
| 578 | 581 | <ComboboxTrigger |
| 579 | 582 | render={<Button variant="ghost" size="xs" />} |
| 580 | className="text-muted-foreground/70 hover:text-foreground/80" | |
| 583 | className={cn("min-w-0 text-muted-foreground/70 hover:text-foreground/80", className)} | |
| 581 | 584 | disabled={(isBranchesSearchPending && branches.length === 0) || isBranchActionPending} |
| 582 | 585 | > |
| 583 | <span className="max-w-[240px] truncate">{triggerLabel}</span> | |
| 584 | <ChevronDownIcon /> | |
| 586 | <span className="min-w-0 max-w-[240px] truncate">{triggerLabel}</span> | |
| 587 | <ChevronDownIcon className="shrink-0" /> | |
| 585 | 588 | </ComboboxTrigger> |
| 586 | 589 | <ComboboxPopup align="end" side="top" className="w-80"> |
| 587 | 590 | <div className="border-b p-1"> |
apps/web/src/components/BranchToolbarEnvModeSelector.tsx+1| ... | ... | @@ -58,6 +58,7 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe |
| 58 | 58 | |
| 59 | 59 | return ( |
| 60 | 60 | <Select |
| 61 | modal={false} | |
| 61 | 62 | value={effectiveEnvMode} |
| 62 | 63 | onValueChange={(value) => onEnvModeChange(value as EnvMode)} |
| 63 | 64 | items={envModeItems} |
apps/web/src/components/BranchToolbarEnvironmentSelector.tsx+1| ... | ... | @@ -54,6 +54,7 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir |
| 54 | 54 | |
| 55 | 55 | return ( |
| 56 | 56 | <Select |
| 57 | modal={false} | |
| 57 | 58 | value={environmentId} |
| 58 | 59 | onValueChange={(value) => onEnvironmentChange(value as EnvironmentId)} |
| 59 | 60 | items={environmentItems} |
apps/web/src/components/ChatView.tsx+37-30| ... | ... | @@ -3281,14 +3281,14 @@ export default function ChatView(props: ChatViewProps) { |
| 3281 | 3281 | {/* Top bar */} |
| 3282 | 3282 | <header |
| 3283 | 3283 | className={cn( |
| 3284 | "border-b border-border px-3 sm:px-5", | |
| 3284 | "border-b border-border", | |
| 3285 | 3285 | isElectron |
| 3286 | 3286 | ? cn( |
| 3287 | "drag-region flex h-[52px] items-center wco:h-[env(titlebar-area-height)]", | |
| 3287 | "drag-region flex h-[52px] items-center px-3 sm:px-5 wco:h-[env(titlebar-area-height)]", | |
| 3288 | 3288 | reserveTitleBarControlInset && |
| 3289 | 3289 | "wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]", |
| 3290 | 3290 | ) |
| 3291 | : "py-2 sm:py-3", | |
| 3291 | : "pb-2 pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] pt-2 sm:pb-3 sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)] sm:pt-3", | |
| 3292 | 3292 | )} |
| 3293 | 3293 | > |
| 3294 | 3294 | <ChatHeader |
| ... | ... | @@ -3374,7 +3374,14 @@ export default function ChatView(props: ChatViewProps) { |
| 3374 | 3374 | </div> |
| 3375 | 3375 | |
| 3376 | 3376 | {/* Input bar */} |
| 3377 | <div className={cn("px-3 pt-1.5 sm:px-5 sm:pt-2", isGitRepo ? "pb-1" : "pb-3 sm:pb-4")}> | |
| 3377 | <div | |
| 3378 | className={cn( | |
| 3379 | "pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] pt-1.5 sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)] sm:pt-2", | |
| 3380 | isGitRepo | |
| 3381 | ? "pb-[calc(env(safe-area-inset-bottom)+0.25rem)]" | |
| 3382 | : "pb-[calc(env(safe-area-inset-bottom)+0.75rem)] sm:pb-[calc(env(safe-area-inset-bottom)+1rem)]", | |
| 3383 | )} | |
| 3384 | > | |
| 3378 | 3385 | <ChatComposer |
| 3379 | 3386 | ref={composerRef} |
| 3380 | 3387 | composerDraftTarget={composerDraftTarget} |
| ... | ... | @@ -3443,34 +3450,34 @@ export default function ChatView(props: ChatViewProps) { |
| 3443 | 3450 | setThreadError={setThreadError} |
| 3444 | 3451 | onExpandImage={onExpandTimelineImage} |
| 3445 | 3452 | /> |
| 3453 | {isGitRepo && ( | |
| 3454 | <BranchToolbar | |
| 3455 | environmentId={activeThread.environmentId} | |
| 3456 | threadId={activeThread.id} | |
| 3457 | {...(routeKind === "draft" && draftId ? { draftId } : {})} | |
| 3458 | onEnvModeChange={onEnvModeChange} | |
| 3459 | {...(canOverrideServerThreadEnvMode ? { effectiveEnvModeOverride: envMode } : {})} | |
| 3460 | {...(canOverrideServerThreadEnvMode | |
| 3461 | ? { | |
| 3462 | activeThreadBranchOverride: activeThreadBranch, | |
| 3463 | onActiveThreadBranchOverrideChange: setPendingServerThreadBranch, | |
| 3464 | } | |
| 3465 | : {})} | |
| 3466 | envLocked={envLocked} | |
| 3467 | onComposerFocusRequest={scheduleComposerFocus} | |
| 3468 | {...(canCheckoutPullRequestIntoThread | |
| 3469 | ? { onCheckoutPullRequestRequest: openPullRequestDialog } | |
| 3470 | : {})} | |
| 3471 | {...(hasMultipleEnvironments | |
| 3472 | ? { | |
| 3473 | availableEnvironments: logicalProjectEnvironments, | |
| 3474 | onEnvironmentChange, | |
| 3475 | } | |
| 3476 | : {})} | |
| 3477 | /> | |
| 3478 | )} | |
| 3446 | 3479 | </div> |
| 3447 | 3480 | |
| 3448 | {isGitRepo && ( | |
| 3449 | <BranchToolbar | |
| 3450 | environmentId={activeThread.environmentId} | |
| 3451 | threadId={activeThread.id} | |
| 3452 | {...(routeKind === "draft" && draftId ? { draftId } : {})} | |
| 3453 | onEnvModeChange={onEnvModeChange} | |
| 3454 | {...(canOverrideServerThreadEnvMode ? { effectiveEnvModeOverride: envMode } : {})} | |
| 3455 | {...(canOverrideServerThreadEnvMode | |
| 3456 | ? { | |
| 3457 | activeThreadBranchOverride: activeThreadBranch, | |
| 3458 | onActiveThreadBranchOverrideChange: setPendingServerThreadBranch, | |
| 3459 | } | |
| 3460 | : {})} | |
| 3461 | envLocked={envLocked} | |
| 3462 | onComposerFocusRequest={scheduleComposerFocus} | |
| 3463 | {...(canCheckoutPullRequestIntoThread | |
| 3464 | ? { onCheckoutPullRequestRequest: openPullRequestDialog } | |
| 3465 | : {})} | |
| 3466 | {...(hasMultipleEnvironments | |
| 3467 | ? { | |
| 3468 | availableEnvironments: logicalProjectEnvironments, | |
| 3469 | onEnvironmentChange, | |
| 3470 | } | |
| 3471 | : {})} | |
| 3472 | /> | |
| 3473 | )} | |
| 3474 | 3481 | {pullRequestDialogState ? ( |
| 3475 | 3482 | <PullRequestThreadDialog |
| 3476 | 3483 | key={pullRequestDialogState.key} |
apps/web/src/components/ComposerPromptEditor.tsx+3-3| ... | ... | @@ -1490,7 +1490,7 @@ function ComposerPromptEditorInner({ |
| 1490 | 1490 | const rootElement = editor.getRootElement(); |
| 1491 | 1491 | if (!rootElement) return; |
| 1492 | 1492 | const boundedCursor = clampCollapsedComposerCursor(snapshotRef.current.value, nextCursor); |
| 1493 | rootElement.focus(); | |
| 1493 | rootElement.focus({ preventScroll: true }); | |
| 1494 | 1494 | editor.update(() => { |
| 1495 | 1495 | $setSelectionAtComposerOffset(boundedCursor); |
| 1496 | 1496 | }); |
| ... | ... | @@ -1621,7 +1621,7 @@ function ComposerPromptEditorInner({ |
| 1621 | 1621 | contentEditable={ |
| 1622 | 1622 | <ContentEditable |
| 1623 | 1623 | className={cn( |
| 1624 | "block max-h-[200px] min-h-17.5 w-full overflow-y-auto whitespace-pre-wrap wrap-break-word bg-transparent text-base sm:text-[14px] leading-relaxed text-foreground focus:outline-none", | |
| 1624 | "block max-h-[200px] min-h-17.5 w-full overflow-y-auto whitespace-pre-wrap wrap-break-word bg-transparent text-[16px] leading-relaxed text-foreground focus:outline-none sm:text-[14px]", | |
| 1625 | 1625 | className, |
| 1626 | 1626 | )} |
| 1627 | 1627 | data-testid="composer-editor" |
| ... | ... | @@ -1632,7 +1632,7 @@ function ComposerPromptEditorInner({ |
| 1632 | 1632 | } |
| 1633 | 1633 | placeholder={ |
| 1634 | 1634 | terminalContexts.length > 0 ? null : ( |
| 1635 | <div className="pointer-events-none absolute inset-0 text-base sm:text-[14px] leading-relaxed text-muted-foreground/35"> | |
| 1635 | <div className="pointer-events-none absolute inset-0 text-[16px] leading-relaxed text-muted-foreground/35 sm:text-[14px]"> | |
| 1636 | 1636 | {placeholder} |
| 1637 | 1637 | </div> |
| 1638 | 1638 | ) |
apps/web/src/components/chat/ChatHeader.tsx+6-1| ... | ... | @@ -16,6 +16,7 @@ import ProjectScriptsControl, { type NewProjectScriptInput } from "../ProjectScr |
| 16 | 16 | import { Toggle } from "../ui/toggle"; |
| 17 | 17 | import { SidebarTrigger } from "../ui/sidebar"; |
| 18 | 18 | import { OpenInPicker } from "./OpenInPicker"; |
| 19 | import { usePrimaryEnvironmentId } from "../../environments/primary"; | |
| 19 | 20 | |
| 20 | 21 | interface ChatHeaderProps { |
| 21 | 22 | activeThreadEnvironmentId: EnvironmentId; |
| ... | ... | @@ -68,6 +69,10 @@ export const ChatHeader = memo(function ChatHeader({ |
| 68 | 69 | onToggleTerminal, |
| 69 | 70 | onToggleDiff, |
| 70 | 71 | }: ChatHeaderProps) { |
| 72 | const primaryEnvironmentId = usePrimaryEnvironmentId(); | |
| 73 | const isRemoteEnvironment = | |
| 74 | primaryEnvironmentId !== null && activeThreadEnvironmentId !== primaryEnvironmentId; | |
| 75 | ||
| 71 | 76 | return ( |
| 72 | 77 | <div className="@container/header-actions flex min-w-0 flex-1 items-center gap-2"> |
| 73 | 78 | <div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden sm:gap-3"> |
| ... | ... | @@ -101,7 +106,7 @@ export const ChatHeader = memo(function ChatHeader({ |
| 101 | 106 | onDeleteScript={onDeleteProjectScript} |
| 102 | 107 | /> |
| 103 | 108 | )} |
| 104 | {activeProjectName && ( | |
| 109 | {activeProjectName && !isRemoteEnvironment && ( | |
| 105 | 110 | <OpenInPicker |
| 106 | 111 | keybindings={keybindings} |
| 107 | 112 | availableEditors={availableEditors} |
apps/web/src/components/ui/sidebar.tsx+8-1| ... | ... | @@ -245,7 +245,14 @@ function Sidebar({ |
| 245 | 245 | <SheetTitle>Sidebar</SheetTitle> |
| 246 | 246 | <SheetDescription>Displays the mobile sidebar.</SheetDescription> |
| 247 | 247 | </SheetHeader> |
| 248 | <div className="flex h-full w-full flex-col">{children}</div> | |
| 248 | <div | |
| 249 | className={cn( | |
| 250 | "flex h-full w-full flex-col pb-safe pt-safe", | |
| 251 | side === "left" ? "pl-safe" : "pr-safe", | |
| 252 | )} | |
| 253 | > | |
| 254 | {children} | |
| 255 | </div> | |
| 249 | 256 | </SheetPopup> |
| 250 | 257 | </Sheet> |
| 251 | 258 | </SidebarInstanceContext.Provider> |
apps/web/src/index.css+28-3| ... | ... | @@ -58,6 +58,22 @@ |
| 58 | 58 | } |
| 59 | 59 | } |
| 60 | 60 | |
| 61 | /* Safe-area inset utilities for surfaces that opt into edge-to-edge rendering. | |
| 62 | Insets resolve to 0 when the browser already constrains the layout viewport | |
| 63 | to the safe area. */ | |
| 64 | @utility pt-safe { | |
| 65 | padding-top: max(env(safe-area-inset-top), 0px); | |
| 66 | } | |
| 67 | @utility pb-safe { | |
| 68 | padding-bottom: max(env(safe-area-inset-bottom), 0px); | |
| 69 | } | |
| 70 | @utility pl-safe { | |
| 71 | padding-left: max(env(safe-area-inset-left), 0px); | |
| 72 | } | |
| 73 | @utility pr-safe { | |
| 74 | padding-right: max(env(safe-area-inset-right), 0px); | |
| 75 | } | |
| 76 | ||
| 61 | 77 | /* Suppress all transitions during theme changes */ |
| 62 | 78 | .no-transitions, |
| 63 | 79 | .no-transitions *, |
| ... | ... | @@ -138,19 +154,28 @@ body { |
| 138 | 154 | sans-serif; |
| 139 | 155 | margin: 0; |
| 140 | 156 | padding: 0; |
| 141 | min-height: 100vh; | |
| 142 | 157 | } |
| 143 | 158 | |
| 144 | 159 | html, |
| 145 | body, | |
| 160 | body { | |
| 161 | min-height: calc(100svh + env(safe-area-inset-top)); | |
| 162 | overscroll-behavior: none; | |
| 163 | } | |
| 164 | ||
| 165 | body { | |
| 166 | height: 100%; | |
| 167 | overflow: hidden; | |
| 168 | } | |
| 169 | ||
| 146 | 170 | #root { |
| 147 | 171 | height: 100%; |
| 148 | 172 | width: 100%; |
| 149 | 173 | max-width: 100%; |
| 150 | overflow-y: hidden; | |
| 151 | 174 | overflow-x: hidden; |
| 152 | 175 | overflow-x: clip; |
| 176 | overflow-y: hidden; | |
| 153 | 177 | overscroll-behavior-y: none; |
| 178 | padding-top: max(env(safe-area-inset-top), 0px); | |
| 154 | 179 | } |
| 155 | 180 | |
| 156 | 181 | body::after { |
apps/web/src/routes/_chat.$environmentId.$threadId.tsx+2-2| ... | ... | @@ -240,7 +240,7 @@ function ChatThreadRouteView() { |
| 240 | 240 | if (!shouldUseDiffSheet) { |
| 241 | 241 | return ( |
| 242 | 242 | <> |
| 243 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> | |
| 243 | <SidebarInset className="h-svh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground md:h-dvh"> | |
| 244 | 244 | <ChatView |
| 245 | 245 | environmentId={threadRef.environmentId} |
| 246 | 246 | threadId={threadRef.threadId} |
| ... | ... | @@ -261,7 +261,7 @@ function ChatThreadRouteView() { |
| 261 | 261 | |
| 262 | 262 | return ( |
| 263 | 263 | <> |
| 264 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> | |
| 264 | <SidebarInset className="h-svh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground md:h-dvh"> | |
| 265 | 265 | <ChatView |
| 266 | 266 | environmentId={threadRef.environmentId} |
| 267 | 267 | threadId={threadRef.threadId} |
apps/web/src/routes/_chat.draft.$draftId.tsx+2-2| ... | ... | @@ -55,7 +55,7 @@ function DraftChatThreadRouteView() { |
| 55 | 55 | |
| 56 | 56 | if (canonicalThreadRef) { |
| 57 | 57 | return ( |
| 58 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> | |
| 58 | <SidebarInset className="h-svh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground md:h-dvh"> | |
| 59 | 59 | <ChatView |
| 60 | 60 | environmentId={canonicalThreadRef.environmentId} |
| 61 | 61 | threadId={canonicalThreadRef.threadId} |
| ... | ... | @@ -70,7 +70,7 @@ function DraftChatThreadRouteView() { |
| 70 | 70 | } |
| 71 | 71 | |
| 72 | 72 | return ( |
| 73 | <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground"> | |
| 73 | <SidebarInset className="h-svh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground md:h-dvh"> | |
| 74 | 74 | <ChatView |
| 75 | 75 | draftId={draftId} |
| 76 | 76 | environmentId={draftSession.environmentId} |