| author | |
| committer | |
| log | c5ff51ec1fff1a9f0447b0336d645ab0f089db62 |
| tree | f0659ec536f281a1ea4fbd1e4323785aafc81f67 |
| parent | 2d31cb022dee43e5a729273a6936228f30077e29 |
40 files changed, 605 insertions(+), 441 deletions(-)
apps/web/src/components/AppSidebarLayout.tsx+2-1| ... | @@ -106,6 +106,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { | ... | @@ -106,6 +106,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { |
| 106 | const pathname = useLocation({ select: (location) => location.pathname }); | 106 | const pathname = useLocation({ select: (location) => location.pathname }); |
| 107 | const isOnSettings = pathname === "/settings" || pathname.startsWith("/settings/"); | 107 | const isOnSettings = pathname === "/settings" || pathname.startsWith("/settings/"); |
| 108 | const useSidebarV2 = sidebarV2Enabled && !isOnSettings; | 108 | const useSidebarV2 = sidebarV2Enabled && !isOnSettings; |
| 109 | const useSidebarV2Theme = useSidebarV2 || isOnSettings; | ||
| 109 | const isMacosDesktop = isElectron && isMacPlatform(navigator.platform); | 110 | const isMacosDesktop = isElectron && isMacPlatform(navigator.platform); |
| 110 | const [sidebarWidth, setSidebarWidth] = useState(readInitialThreadSidebarWidth); | 111 | const [sidebarWidth, setSidebarWidth] = useState(readInitialThreadSidebarWidth); |
| 111 | const sidebarMaximumWidth = resolveThreadSidebarMaximumWidth(window.innerWidth); | 112 | const sidebarMaximumWidth = resolveThreadSidebarMaximumWidth(window.innerWidth); |
| ... | @@ -165,7 +166,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { | ... | @@ -165,7 +166,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { |
| 165 | side="left" | 166 | side="left" |
| 166 | collapsible="offcanvas" | 167 | collapsible="offcanvas" |
| 167 | data-app-sidebar="" | 168 | data-app-sidebar="" |
| 168 | data-sidebar-version={useSidebarV2 ? "v2" : "v1"} | 169 | data-sidebar-version={useSidebarV2Theme ? "v2" : "v1"} |
| 169 | className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground" | 170 | className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground" |
| 170 | resizable={{ | 171 | resizable={{ |
| 171 | maxWidth: sidebarMaximumWidth, | 172 | maxWidth: sidebarMaximumWidth, |
apps/web/src/components/BranchToolbar.tsx+1-1| ... | @@ -248,7 +248,7 @@ export const BranchToolbar = memo(function BranchToolbar({ | ... | @@ -248,7 +248,7 @@ export const BranchToolbar = memo(function BranchToolbar({ |
| 248 | if (!hasActiveThread || !activeProject) return null; | 248 | if (!hasActiveThread || !activeProject) return null; |
| 249 | 249 | ||
| 250 | return ( | 250 | return ( |
| 251 | <div className="mx-auto flex w-full max-w-3xl items-center gap-2 px-2.5 pb-3 pt-1 sm:px-3"> | 251 | <div className="chat-composer-context-strip -mt-2 mx-auto flex w-[calc(100%-1.5rem)] max-w-[calc(48rem-1.5rem)] items-center gap-2 pt-3 pe-2 pb-1 ps-2"> |
| 252 | {isMobile ? ( | 252 | {isMobile ? ( |
| 253 | <MobileRunContextSelector | 253 | <MobileRunContextSelector |
| 254 | envLocked={envLocked} | 254 | envLocked={envLocked} |
apps/web/src/components/ChatView.tsx+13-21| ... | @@ -2320,6 +2320,11 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -2320,6 +2320,11 @@ function ChatViewContent(props: ChatViewProps) { |
| 2320 | const defaultInstanceId = defaultInstanceIdForDriver(selectedProvider); | 2320 | const defaultInstanceId = defaultInstanceIdForDriver(selectedProvider); |
| 2321 | return providerStatuses.find((status) => status.instanceId === defaultInstanceId) ?? null; | 2321 | return providerStatuses.find((status) => status.instanceId === defaultInstanceId) ?? null; |
| 2322 | }, [activeProviderInstanceId, providerStatuses, selectedProvider]); | 2322 | }, [activeProviderInstanceId, providerStatuses, selectedProvider]); |
| 2323 | const hasTimelineTopBanner = | ||
| 2324 | Boolean(threadError) || | ||
| 2325 | (activeProviderStatus !== null && | ||
| 2326 | activeProviderStatus.status !== "ready" && | ||
| 2327 | activeProviderStatus.status !== "disabled"); | ||
| 2323 | const activeProjectCwd = activeProject?.workspaceRoot ?? null; | 2328 | const activeProjectCwd = activeProject?.workspaceRoot ?? null; |
| 2324 | const activeThreadWorktreePath = activeThread?.worktreePath ?? null; | 2329 | const activeThreadWorktreePath = activeThread?.worktreePath ?? null; |
| 2325 | const activeWorkspaceRoot = activeThreadWorktreePath ?? activeProjectCwd ?? undefined; | 2330 | const activeWorkspaceRoot = activeThreadWorktreePath ?? activeProjectCwd ?? undefined; |
| ... | @@ -5412,7 +5417,7 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -5412,7 +5417,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 5412 | <header | 5417 | <header |
| 5413 | data-chat-header | 5418 | data-chat-header |
| 5414 | className={cn( | 5419 | className={cn( |
| 5415 | "border-b border-border transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none", | 5420 | "bg-background transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none", |
| 5416 | isElectron | 5421 | isElectron |
| 5417 | ? cn( | 5422 | ? cn( |
| 5418 | "workspace-topbar drag-region relative px-3 sm:px-5", | 5423 | "workspace-topbar drag-region relative px-3 sm:px-5", |
| ... | @@ -5448,8 +5453,6 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -5448,8 +5453,6 @@ function ChatViewContent(props: ChatViewProps) { |
| 5448 | /> | 5453 | /> |
| 5449 | </header> | 5454 | </header> |
| 5450 | 5455 | ||
| 5451 | {/* Error banner */} | ||
| 5452 | <ProviderStatusBanner status={activeProviderStatus} /> | ||
| 5453 | <ThreadErrorBanner | 5456 | <ThreadErrorBanner |
| 5454 | error={threadError} | 5457 | error={threadError} |
| 5455 | onDismiss={() => setThreadError(activeThread.id, null)} | 5458 | onDismiss={() => setThreadError(activeThread.id, null)} |
| ... | @@ -5458,6 +5461,10 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -5458,6 +5461,10 @@ function ChatViewContent(props: ChatViewProps) { |
| 5458 | <div className="flex min-h-0 min-w-0 flex-1"> | 5461 | <div className="flex min-h-0 min-w-0 flex-1"> |
| 5459 | {/* Chat column */} | 5462 | {/* Chat column */} |
| 5460 | <div className="relative flex min-h-0 min-w-0 flex-1 flex-col"> | 5463 | <div className="relative flex min-h-0 min-w-0 flex-1 flex-col"> |
| 5464 | {/* Provider status overlays the timeline without changing its content height. */} | ||
| 5465 | <div className="pointer-events-none absolute inset-x-0 top-0 z-20"> | ||
| 5466 | <ProviderStatusBanner status={activeProviderStatus} /> | ||
| 5467 | </div> | ||
| 5461 | {/* Messages Wrapper */} | 5468 | {/* Messages Wrapper */} |
| 5462 | <div className="relative flex min-h-0 flex-1 flex-col"> | 5469 | <div className="relative flex min-h-0 flex-1 flex-col"> |
| 5463 | {/* Messages — LegendList handles virtualization and scrolling internally */} | 5470 | {/* Messages — LegendList handles virtualization and scrolling internally */} |
| ... | @@ -5494,6 +5501,7 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -5494,6 +5501,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 5494 | onIsAtEndChange={onIsAtEndChange} | 5501 | onIsAtEndChange={onIsAtEndChange} |
| 5495 | onManualNavigation={cancelTimelineLiveFollowForUserNavigation} | 5502 | onManualNavigation={cancelTimelineLiveFollowForUserNavigation} |
| 5496 | hideEmptyPlaceholder={isDraftHeroState} | 5503 | hideEmptyPlaceholder={isDraftHeroState} |
| 5504 | topFadeEnabled={!hasTimelineTopBanner} | ||
| 5497 | /> | 5505 | /> |
| 5498 | 5506 | ||
| 5499 | {/* scroll to end pill — shown when user has scrolled away from the live edge */} | 5507 | {/* scroll to end pill — shown when user has scrolled away from the live edge */} |
| ... | @@ -5526,17 +5534,6 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -5526,17 +5534,6 @@ function ChatViewContent(props: ChatViewProps) { |
| 5526 | : "pointer-events-none absolute inset-x-0 bottom-0 z-20 pt-1.5 sm:pt-2" | 5534 | : "pointer-events-none absolute inset-x-0 bottom-0 z-20 pt-1.5 sm:pt-2" |
| 5527 | } | 5535 | } |
| 5528 | > | 5536 | > |
| 5529 | {!isDraftHeroState ? ( | ||
| 5530 | <div | ||
| 5531 | key="docked-composer-blur" | ||
| 5532 | aria-hidden="true" | ||
| 5533 | className="chat-composer-horizontal-inset pointer-events-none absolute inset-x-0 top-1.5 bottom-0 z-0 sm:top-2" | ||
| 5534 | > | ||
| 5535 | <div className="relative mx-auto h-full w-full max-w-3xl overflow-clip rounded-t-[20px]"> | ||
| 5536 | <div className="chat-composer-shared-blur absolute -inset-8" /> | ||
| 5537 | </div> | ||
| 5538 | </div> | ||
| 5539 | ) : null} | ||
| 5540 | <div | 5537 | <div |
| 5541 | ref={attachDraftHeroTransitionGroupRef} | 5538 | ref={attachDraftHeroTransitionGroupRef} |
| 5542 | className="chat-composer-horizontal-inset w-full" | 5539 | className="chat-composer-horizontal-inset w-full" |
| ... | @@ -5574,7 +5571,7 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -5574,7 +5571,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 5574 | > | 5571 | > |
| 5575 | <div | 5572 | <div |
| 5576 | ref={attachDraftHeroComposerAnchorRef} | 5573 | ref={attachDraftHeroComposerAnchorRef} |
| 5577 | className="relative z-10 mx-auto w-full max-w-3xl" | 5574 | className="relative z-10 isolate mx-auto w-full max-w-3xl before:pointer-events-none before:absolute before:inset-x-0 before:bottom-0 before:-z-10 before:h-2 before:rounded-b-[22px] before:bg-background" |
| 5578 | > | 5575 | > |
| 5579 | <ChatComposer | 5576 | <ChatComposer |
| 5580 | composerRef={composerRef} | 5577 | composerRef={composerRef} |
| ... | @@ -5658,12 +5655,7 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -5658,12 +5655,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 5658 | > | 5655 | > |
| 5659 | <div | 5656 | <div |
| 5660 | data-terminal-open={terminalUiState.terminalOpen ? "true" : undefined} | 5657 | data-terminal-open={terminalUiState.terminalOpen ? "true" : undefined} |
| 5661 | className={cn( | 5658 | className="chat-composer-lower-chrome relative z-0 pb-[calc(env(safe-area-inset-bottom)+1rem)] sm:pb-[calc(env(safe-area-inset-bottom)+1.25rem)]" |
| 5662 | "chat-composer-lower-chrome relative z-10", | ||
| 5663 | isGitRepo | ||
| 5664 | ? "pb-[calc(env(safe-area-inset-bottom)+0.25rem)]" | ||
| 5665 | : "pb-[calc(env(safe-area-inset-bottom)+0.75rem)] sm:pb-[calc(env(safe-area-inset-bottom)+1rem)]", | ||
| 5666 | )} | ||
| 5667 | > | 5659 | > |
| 5668 | {isGitRepo && ( | 5660 | {isGitRepo && ( |
| 5669 | <div className="pointer-events-auto"> | 5661 | <div className="pointer-events-auto"> |
apps/web/src/components/CommandPalette.tsx+1-1| ... | @@ -1948,7 +1948,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1948,7 +1948,7 @@ function OpenCommandPaletteDialog(props: { |
| 1948 | <Button | 1948 | <Button |
| 1949 | variant="ghost" | 1949 | variant="ghost" |
| 1950 | size="xs" | 1950 | size="xs" |
| 1951 | className="h-auto px-2 text-zinc-700 text-xs hover:bg-transparent hover:text-foreground dark:text-zinc-300" | 1951 | className="h-auto px-2 text-muted-foreground text-xs hover:bg-transparent hover:text-foreground" |
| 1952 | disabled={isPickingProjectFolder} | 1952 | disabled={isPickingProjectFolder} |
| 1953 | onClick={() => { | 1953 | onClick={() => { |
| 1954 | void handleOpenProjectFromFileManager(); | 1954 | void handleOpenProjectFromFileManager(); |
apps/web/src/components/DiffPanel.tsx+24-12| ... | @@ -521,27 +521,37 @@ export default function DiffPanel({ | ... | @@ -521,27 +521,37 @@ export default function DiffPanel({ |
| 521 | <ChevronDownIcon className="size-3.5 shrink-0 text-muted-foreground" /> | 521 | <ChevronDownIcon className="size-3.5 shrink-0 text-muted-foreground" /> |
| 522 | </DropdownMenuTrigger> | 522 | </DropdownMenuTrigger> |
| 523 | <DropdownMenuContent align="start" className="w-60"> | 523 | <DropdownMenuContent align="start" className="w-60"> |
| 524 | <DropdownMenuItem onClick={() => selectGitScope("unstaged")}> | 524 | <DropdownMenuItem |
| 525 | className={ | ||
| 526 | selectedTurnId === null && selectedGitScope === "unstaged" | ||
| 527 | ? "bg-foreground/[0.08]" | ||
| 528 | : undefined | ||
| 529 | } | ||
| 530 | onClick={() => selectGitScope("unstaged")} | ||
| 531 | > | ||
| 525 | <span>Working tree</span> | 532 | <span>Working tree</span> |
| 526 | {selectedTurnId === null && selectedGitScope === "unstaged" && ( | ||
| 527 | <CheckIcon className="ml-auto" /> | ||
| 528 | )} | ||
| 529 | </DropdownMenuItem> | 533 | </DropdownMenuItem> |
| 530 | <DropdownMenuItem onClick={() => selectGitScope("branch")}> | 534 | <DropdownMenuItem |
| 535 | className={ | ||
| 536 | selectedTurnId === null && selectedGitScope === "branch" | ||
| 537 | ? "bg-foreground/[0.08]" | ||
| 538 | : undefined | ||
| 539 | } | ||
| 540 | onClick={() => selectGitScope("branch")} | ||
| 541 | > | ||
| 531 | <span>Branch changes</span> | 542 | <span>Branch changes</span> |
| 532 | {selectedTurnId === null && selectedGitScope === "branch" && ( | ||
| 533 | <CheckIcon className="ml-auto" /> | ||
| 534 | )} | ||
| 535 | </DropdownMenuItem> | 543 | </DropdownMenuItem> |
| 536 | <DropdownMenuItem | 544 | <DropdownMenuItem |
| 545 | className={ | ||
| 546 | selectedTurnId !== null && selectedTurn?.turnId === latestTurn?.turnId | ||
| 547 | ? "bg-foreground/[0.08]" | ||
| 548 | : undefined | ||
| 549 | } | ||
| 537 | onClick={() => { | 550 | onClick={() => { |
| 538 | if (latestTurn) selectTurn(latestTurn.turnId); | 551 | if (latestTurn) selectTurn(latestTurn.turnId); |
| 539 | }} | 552 | }} |
| 540 | > | 553 | > |
| 541 | <span>Latest turn</span> | 554 | <span>Latest turn</span> |
| 542 | {selectedTurnId !== null && selectedTurn?.turnId === latestTurn?.turnId && ( | ||
| 543 | <CheckIcon className="ml-auto" /> | ||
| 544 | )} | ||
| 545 | </DropdownMenuItem> | 555 | </DropdownMenuItem> |
| 546 | <DropdownMenuSub> | 556 | <DropdownMenuSub> |
| 547 | <DropdownMenuSubTrigger>Turn</DropdownMenuSubTrigger> | 557 | <DropdownMenuSubTrigger>Turn</DropdownMenuSubTrigger> |
| ... | @@ -554,13 +564,15 @@ export default function DiffPanel({ | ... | @@ -554,13 +564,15 @@ export default function DiffPanel({ |
| 554 | return ( | 564 | return ( |
| 555 | <DropdownMenuItem | 565 | <DropdownMenuItem |
| 556 | key={summary.turnId} | 566 | key={summary.turnId} |
| 567 | className={ | ||
| 568 | summary.turnId === selectedTurn?.turnId ? "bg-foreground/[0.08]" : undefined | ||
| 569 | } | ||
| 557 | onClick={() => selectTurn(summary.turnId)} | 570 | onClick={() => selectTurn(summary.turnId)} |
| 558 | > | 571 | > |
| 559 | <span>Turn {turnCount}</span> | 572 | <span>Turn {turnCount}</span> |
| 560 | <span className="ml-auto text-xs tabular-nums text-muted-foreground"> | 573 | <span className="ml-auto text-xs tabular-nums text-muted-foreground"> |
| 561 | {formatShortTimestamp(summary.completedAt, settings.timestampFormat)} | 574 | {formatShortTimestamp(summary.completedAt, settings.timestampFormat)} |
| 562 | </span> | 575 | </span> |
| 563 | {summary.turnId === selectedTurn?.turnId && <CheckIcon className="ml-1" />} | ||
| 564 | </DropdownMenuItem> | 576 | </DropdownMenuItem> |
| 565 | ); | 577 | ); |
| 566 | })} | 578 | })} |
apps/web/src/components/GitActionsControl.tsx+9-9| ... | @@ -544,9 +544,9 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { | ... | @@ -544,9 +544,9 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { |
| 544 | 544 | ||
| 545 | return ( | 545 | return ( |
| 546 | <Dialog open={props.open} onOpenChange={handleOpenChange}> | 546 | <Dialog open={props.open} onOpenChange={handleOpenChange}> |
| 547 | <DialogPopup className="max-w-xl overflow-hidden"> | 547 | <DialogPopup className="workflow-dialog-surface max-w-xl overflow-hidden before:hidden"> |
| 548 | <div className="flex min-h-0 flex-col overflow-hidden border-foreground/10 bg-background shadow-2xl"> | 548 | <div className="flex min-h-0 flex-col overflow-hidden border-foreground/10 bg-transparent"> |
| 549 | <DialogHeader className="border-b border-border/70 bg-background"> | 549 | <DialogHeader className="border-b border-border/70 bg-foreground/[0.025]"> |
| 550 | <DialogTitle>Publish repository</DialogTitle> | 550 | <DialogTitle>Publish repository</DialogTitle> |
| 551 | <DialogDescription> | 551 | <DialogDescription> |
| 552 | Pick where to host it, then point us at a repo to push to. | 552 | Pick where to host it, then point us at a repo to push to. |
| ... | @@ -913,7 +913,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { | ... | @@ -913,7 +913,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { |
| 913 | </AnimatedHeight> | 913 | </AnimatedHeight> |
| 914 | </DialogPanel> | 914 | </DialogPanel> |
| 915 | 915 | ||
| 916 | <DialogFooter> | 916 | <DialogFooter className="dark:border-white/5 dark:bg-white/[0.025]"> |
| 917 | {publishWizardStep === 2 ? ( | 917 | {publishWizardStep === 2 ? ( |
| 918 | <Button size="sm" onClick={() => handleOpenChange(false)}> | 918 | <Button size="sm" onClick={() => handleOpenChange(false)}> |
| 919 | Done | 919 | Done |
| ... | @@ -1736,7 +1736,7 @@ export default function GitActionsControl({ | ... | @@ -1736,7 +1736,7 @@ export default function GitActionsControl({ |
| 1736 | > | 1736 | > |
| 1737 | <ChevronDownIcon aria-hidden="true" className="size-4" /> | 1737 | <ChevronDownIcon aria-hidden="true" className="size-4" /> |
| 1738 | </MenuTrigger> | 1738 | </MenuTrigger> |
| 1739 | <MenuPopup align="end" className="w-full"> | 1739 | <MenuPopup align="end" className="command-menu-surface w-full"> |
| 1740 | {gitActionMenuItems.map((item) => { | 1740 | {gitActionMenuItems.map((item) => { |
| 1741 | const disabledReason = getMenuActionDisabledReason({ | 1741 | const disabledReason = getMenuActionDisabledReason({ |
| 1742 | item, | 1742 | item, |
| ... | @@ -1825,7 +1825,7 @@ export default function GitActionsControl({ | ... | @@ -1825,7 +1825,7 @@ export default function GitActionsControl({ |
| 1825 | } | 1825 | } |
| 1826 | }} | 1826 | }} |
| 1827 | > | 1827 | > |
| 1828 | <DialogPopup> | 1828 | <DialogPopup className="workflow-dialog-surface before:hidden"> |
| 1829 | <DialogHeader> | 1829 | <DialogHeader> |
| 1830 | <DialogTitle>{COMMIT_DIALOG_TITLE}</DialogTitle> | 1830 | <DialogTitle>{COMMIT_DIALOG_TITLE}</DialogTitle> |
| 1831 | <DialogDescription>{COMMIT_DIALOG_DESCRIPTION}</DialogDescription> | 1831 | <DialogDescription>{COMMIT_DIALOG_DESCRIPTION}</DialogDescription> |
| ... | @@ -1955,7 +1955,7 @@ export default function GitActionsControl({ | ... | @@ -1955,7 +1955,7 @@ export default function GitActionsControl({ |
| 1955 | /> | 1955 | /> |
| 1956 | </div> | 1956 | </div> |
| 1957 | </DialogPanel> | 1957 | </DialogPanel> |
| 1958 | <DialogFooter> | 1958 | <DialogFooter className="dark:border-white/5 dark:bg-white/[0.025]"> |
| 1959 | <Button | 1959 | <Button |
| 1960 | variant="outline" | 1960 | variant="outline" |
| 1961 | size="sm" | 1961 | size="sm" |
| ... | @@ -1998,14 +1998,14 @@ export default function GitActionsControl({ | ... | @@ -1998,14 +1998,14 @@ export default function GitActionsControl({ |
| 1998 | } | 1998 | } |
| 1999 | }} | 1999 | }} |
| 2000 | > | 2000 | > |
| 2001 | <DialogPopup className="max-w-xl"> | 2001 | <DialogPopup className="workflow-dialog-surface max-w-xl before:hidden"> |
| 2002 | <DialogHeader> | 2002 | <DialogHeader> |
| 2003 | <DialogTitle> | 2003 | <DialogTitle> |
| 2004 | {pendingDefaultBranchActionCopy?.title ?? "Run action on default refName?"} | 2004 | {pendingDefaultBranchActionCopy?.title ?? "Run action on default refName?"} |
| 2005 | </DialogTitle> | 2005 | </DialogTitle> |
| 2006 | <DialogDescription>{pendingDefaultBranchActionCopy?.description}</DialogDescription> | 2006 | <DialogDescription>{pendingDefaultBranchActionCopy?.description}</DialogDescription> |
| 2007 | </DialogHeader> | 2007 | </DialogHeader> |
| 2008 | <DialogFooter className="sm:flex-wrap sm:items-center"> | 2008 | <DialogFooter className="dark:border-white/5 dark:bg-white/[0.025] sm:flex-wrap sm:items-center"> |
| 2009 | <Button | 2009 | <Button |
| 2010 | className="w-full sm:mr-auto sm:w-auto" | 2010 | className="w-full sm:mr-auto sm:w-auto" |
| 2011 | variant="outline" | 2011 | variant="outline" |
apps/web/src/components/ProjectScriptsControl.tsx+4-4| ... | @@ -276,7 +276,7 @@ export default function ProjectScriptsControl({ | ... | @@ -276,7 +276,7 @@ export default function ProjectScriptsControl({ |
| 276 | > | 276 | > |
| 277 | <ChevronDownIcon className="size-4" /> | 277 | <ChevronDownIcon className="size-4" /> |
| 278 | </MenuTrigger> | 278 | </MenuTrigger> |
| 279 | <MenuPopup align="end"> | 279 | <MenuPopup align="end" className="command-menu-surface"> |
| 280 | {scripts.map((script) => { | 280 | {scripts.map((script) => { |
| 281 | const shortcutLabel = shortcutLabelForCommand( | 281 | const shortcutLabel = shortcutLabelForCommand( |
| 282 | keybindings, | 282 | keybindings, |
| ... | @@ -364,7 +364,7 @@ export default function ProjectScriptsControl({ | ... | @@ -364,7 +364,7 @@ export default function ProjectScriptsControl({ |
| 364 | }} | 364 | }} |
| 365 | open={dialogOpen} | 365 | open={dialogOpen} |
| 366 | > | 366 | > |
| 367 | <DialogPopup> | 367 | <DialogPopup className="workflow-dialog-surface before:hidden"> |
| 368 | <DialogHeader> | 368 | <DialogHeader> |
| 369 | <DialogTitle>{isEditing ? "Edit Action" : "Add Action"}</DialogTitle> | 369 | <DialogTitle>{isEditing ? "Edit Action" : "Add Action"}</DialogTitle> |
| 370 | <DialogDescription> | 370 | <DialogDescription> |
| ... | @@ -389,7 +389,7 @@ export default function ProjectScriptsControl({ | ... | @@ -389,7 +389,7 @@ export default function ProjectScriptsControl({ |
| 389 | > | 389 | > |
| 390 | <ScriptIcon icon={icon} className="size-4.5" /> | 390 | <ScriptIcon icon={icon} className="size-4.5" /> |
| 391 | </PopoverTrigger> | 391 | </PopoverTrigger> |
| 392 | <PopoverPopup align="start"> | 392 | <PopoverPopup align="start" className="command-menu-surface"> |
| 393 | <div className="grid grid-cols-3 gap-2"> | 393 | <div className="grid grid-cols-3 gap-2"> |
| 394 | {SCRIPT_ICONS.map((entry) => { | 394 | {SCRIPT_ICONS.map((entry) => { |
| 395 | const isSelected = entry.id === icon; | 395 | const isSelected = entry.id === icon; |
| ... | @@ -480,7 +480,7 @@ export default function ProjectScriptsControl({ | ... | @@ -480,7 +480,7 @@ export default function ProjectScriptsControl({ |
| 480 | {validationError && <p className="text-sm text-destructive">{validationError}</p>} | 480 | {validationError && <p className="text-sm text-destructive">{validationError}</p>} |
| 481 | </form> | 481 | </form> |
| 482 | </DialogPanel> | 482 | </DialogPanel> |
| 483 | <DialogFooter> | 483 | <DialogFooter className="dark:border-white/5 dark:bg-white/[0.025]"> |
| 484 | {isEditing && ( | 484 | {isEditing && ( |
| 485 | <Button | 485 | <Button |
| 486 | type="button" | 486 | type="button" |
apps/web/src/components/Sidebar.logic.test.ts+10-12| ... | @@ -834,26 +834,24 @@ describe("resolveThreadStatusPill", () => { | ... | @@ -834,26 +834,24 @@ describe("resolveThreadStatusPill", () => { |
| 834 | }); | 834 | }); |
| 835 | 835 | ||
| 836 | describe("resolveThreadRowClassName", () => { | 836 | describe("resolveThreadRowClassName", () => { |
| 837 | it("uses the darker selected palette when a thread is both selected and active", () => { | 837 | it("uses the active sidebar surface when a thread is both selected and active", () => { |
| 838 | const className = resolveThreadRowClassName({ isActive: true, isSelected: true }); | 838 | const className = resolveThreadRowClassName({ isActive: true, isSelected: true }); |
| 839 | expect(className).toContain("bg-primary/22"); | 839 | expect(className).toContain("bg-sidebar-row-active"); |
| 840 | expect(className).toContain("hover:bg-primary/26"); | 840 | expect(className).toContain("text-sidebar-foreground"); |
| 841 | expect(className).toContain("dark:bg-primary/30"); | 841 | expect(className).not.toContain("bg-primary"); |
| 842 | expect(className).not.toContain("bg-accent/85"); | ||
| 843 | }); | 842 | }); |
| 844 | 843 | ||
| 845 | it("uses selected hover colors for selected threads", () => { | 844 | it("uses selected hover colors for selected threads", () => { |
| 846 | const className = resolveThreadRowClassName({ isActive: false, isSelected: true }); | 845 | const className = resolveThreadRowClassName({ isActive: false, isSelected: true }); |
| 847 | expect(className).toContain("bg-primary/15"); | 846 | expect(className).toContain("bg-sidebar-row-selected"); |
| 848 | expect(className).toContain("hover:bg-primary/19"); | 847 | expect(className).toContain("hover:bg-sidebar-row-active"); |
| 849 | expect(className).toContain("dark:bg-primary/22"); | 848 | expect(className).not.toContain("bg-primary"); |
| 850 | expect(className).not.toContain("hover:bg-accent"); | ||
| 851 | }); | 849 | }); |
| 852 | 850 | ||
| 853 | it("keeps the accent palette for active-only threads", () => { | 851 | it("uses the active sidebar surface for active-only threads", () => { |
| 854 | const className = resolveThreadRowClassName({ isActive: true, isSelected: false }); | 852 | const className = resolveThreadRowClassName({ isActive: true, isSelected: false }); |
| 855 | expect(className).toContain("bg-accent/85"); | 853 | expect(className).toContain("bg-sidebar-row-active"); |
| 856 | expect(className).toContain("hover:bg-accent"); | 854 | expect(className).toContain("hover:bg-sidebar-row-active"); |
| 857 | }); | 855 | }); |
| 858 | }); | 856 | }); |
| 859 | 857 |
apps/web/src/components/Sidebar.logic.ts+8-5| ... | @@ -391,30 +391,33 @@ export function resolveThreadRowClassName(input: { | ... | @@ -391,30 +391,33 @@ export function resolveThreadRowClassName(input: { |
| 391 | isSelected: boolean; | 391 | isSelected: boolean; |
| 392 | }): string { | 392 | }): string { |
| 393 | const baseClassName = | 393 | const baseClassName = |
| 394 | "h-6 w-full translate-x-0 cursor-pointer justify-start px-2 text-left select-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring sm:h-7"; | 394 | "h-8 w-full translate-x-0 cursor-pointer justify-start rounded-md px-2 text-left text-sm select-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"; |
| 395 | 395 | ||
| 396 | if (input.isSelected && input.isActive) { | 396 | if (input.isSelected && input.isActive) { |
| 397 | return cn( | 397 | return cn( |
| 398 | baseClassName, | 398 | baseClassName, |
| 399 | "bg-primary/22 text-foreground font-medium hover:bg-primary/26 hover:text-foreground dark:bg-primary/30 dark:hover:bg-primary/36", | 399 | "bg-sidebar-row-active text-sidebar-foreground font-medium hover:bg-sidebar-row-active hover:text-sidebar-foreground", |
| 400 | ); | 400 | ); |
| 401 | } | 401 | } |
| 402 | 402 | ||
| 403 | if (input.isSelected) { | 403 | if (input.isSelected) { |
| 404 | return cn( | 404 | return cn( |
| 405 | baseClassName, | 405 | baseClassName, |
| 406 | "bg-primary/15 text-foreground hover:bg-primary/19 hover:text-foreground dark:bg-primary/22 dark:hover:bg-primary/28", | 406 | "bg-sidebar-row-selected text-sidebar-foreground hover:bg-sidebar-row-active hover:text-sidebar-foreground", |
| 407 | ); | 407 | ); |
| 408 | } | 408 | } |
| 409 | 409 | ||
| 410 | if (input.isActive) { | 410 | if (input.isActive) { |
| 411 | return cn( | 411 | return cn( |
| 412 | baseClassName, | 412 | baseClassName, |
| 413 | "bg-accent/85 text-foreground font-medium hover:bg-accent hover:text-foreground dark:bg-accent/55 dark:hover:bg-accent/70", | 413 | "bg-sidebar-row-active text-sidebar-foreground font-medium hover:bg-sidebar-row-active hover:text-sidebar-foreground", |
| 414 | ); | 414 | ); |
| 415 | } | 415 | } |
| 416 | 416 | ||
| 417 | return cn(baseClassName, "text-muted-foreground hover:bg-accent hover:text-foreground"); | 417 | return cn( |
| 418 | baseClassName, | ||
| 419 | "text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", | ||
| 420 | ); | ||
| 418 | } | 421 | } |
| 419 | 422 | ||
| 420 | // ── Sidebar v2 status model ───────────────────────────────────────── | 423 | // ── Sidebar v2 status model ───────────────────────────────────────── |
apps/web/src/components/Sidebar.tsx+12-14| ... | @@ -714,7 +714,7 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr | ... | @@ -714,7 +714,7 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr |
| 714 | {renamingThreadKey === threadKey ? ( | 714 | {renamingThreadKey === threadKey ? ( |
| 715 | <input | 715 | <input |
| 716 | ref={handleRenameInputRef} | 716 | ref={handleRenameInputRef} |
| 717 | className="min-w-0 flex-1 truncate text-base sm:text-xs bg-transparent outline-none border border-ring rounded px-0.5" | 717 | className="min-w-0 flex-1 truncate rounded border border-ring bg-transparent px-0.5 text-sm outline-none" |
| 718 | value={renamingTitle} | 718 | value={renamingTitle} |
| 719 | onChange={handleRenameInputChange} | 719 | onChange={handleRenameInputChange} |
| 720 | onKeyDown={handleRenameInputKeyDown} | 720 | onKeyDown={handleRenameInputKeyDown} |
| ... | @@ -727,7 +727,7 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr | ... | @@ -727,7 +727,7 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr |
| 727 | <TooltipTrigger | 727 | <TooltipTrigger |
| 728 | render={ | 728 | render={ |
| 729 | <span | 729 | <span |
| 730 | className="min-w-0 flex-1 truncate text-xs" | 730 | className="min-w-0 flex-1 truncate text-sm" |
| 731 | data-testid={`thread-title-${thread.id}`} | 731 | data-testid={`thread-title-${thread.id}`} |
| 732 | > | 732 | > |
| 733 | {thread.title} | 733 | {thread.title} |
| ... | @@ -982,13 +982,13 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( | ... | @@ -982,13 +982,13 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( |
| 982 | return ( | 982 | return ( |
| 983 | <SidebarMenuSub | 983 | <SidebarMenuSub |
| 984 | ref={attachThreadListAutoAnimateRef} | 984 | ref={attachThreadListAutoAnimateRef} |
| 985 | className="mx-0.5 my-0 w-full translate-x-0 gap-0.5 overflow-hidden px-1 py-0 sm:mx-1 sm:px-1.5" | 985 | className="mx-0.5 my-0 w-full translate-x-0 gap-0.5 overflow-hidden border-l-0 px-1 py-0 sm:mx-1 sm:px-1.5" |
| 986 | > | 986 | > |
| 987 | {shouldShowThreadPanel && showEmptyThreadState ? ( | 987 | {shouldShowThreadPanel && showEmptyThreadState ? ( |
| 988 | <SidebarMenuSubItem className="w-full" data-thread-selection-safe> | 988 | <SidebarMenuSubItem className="w-full" data-thread-selection-safe> |
| 989 | <div | 989 | <div |
| 990 | data-thread-selection-safe | 990 | data-thread-selection-safe |
| 991 | className="flex h-6 w-full translate-x-0 items-center px-2 text-left text-[10px] text-muted-foreground/60" | 991 | className="flex h-8 w-full translate-x-0 items-center px-2 text-left text-xs text-sidebar-muted-foreground/75" |
| 992 | > | 992 | > |
| 993 | <span>No threads yet</span> | 993 | <span>No threads yet</span> |
| 994 | </div> | 994 | </div> |
| ... | @@ -1034,7 +1034,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( | ... | @@ -1034,7 +1034,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( |
| 1034 | render={showMoreButtonRender} | 1034 | render={showMoreButtonRender} |
| 1035 | data-thread-selection-safe | 1035 | data-thread-selection-safe |
| 1036 | size="sm" | 1036 | size="sm" |
| 1037 | className="h-6 w-full translate-x-0 justify-start px-2 text-left text-[10px] text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground/80" | 1037 | className="h-8 w-full translate-x-0 justify-start px-2 text-left text-xs text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 1038 | onClick={() => { | 1038 | onClick={() => { |
| 1039 | expandThreadListForProject(projectKey); | 1039 | expandThreadListForProject(projectKey); |
| 1040 | }} | 1040 | }} |
| ... | @@ -1052,7 +1052,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( | ... | @@ -1052,7 +1052,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( |
| 1052 | render={showLessButtonRender} | 1052 | render={showLessButtonRender} |
| 1053 | data-thread-selection-safe | 1053 | data-thread-selection-safe |
| 1054 | size="sm" | 1054 | size="sm" |
| 1055 | className="h-6 w-full translate-x-0 justify-start px-2 text-left text-[10px] text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground/80" | 1055 | className="h-8 w-full translate-x-0 justify-start px-2 text-left text-xs text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 1056 | onClick={() => { | 1056 | onClick={() => { |
| 1057 | collapseThreadListForProject(projectKey); | 1057 | collapseThreadListForProject(projectKey); |
| 1058 | }} | 1058 | }} |
| ... | @@ -2252,7 +2252,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -2252,7 +2252,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2252 | <SidebarMenuButton | 2252 | <SidebarMenuButton |
| 2253 | ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined} | 2253 | ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined} |
| 2254 | size="sm" | 2254 | size="sm" |
| 2255 | className={`gap-2 px-2 py-1.5 pr-8 text-left hover:bg-sidebar-row-hover group-hover/project-header:bg-sidebar-row-hover group-hover/project-header:text-sidebar-foreground max-sm:pr-14 ${ | 2255 | className={`h-8 gap-2 rounded-md px-2 py-1.5 pr-8 text-left hover:bg-sidebar-row-hover group-hover/project-header:bg-sidebar-row-hover group-hover/project-header:text-sidebar-foreground max-sm:pr-14 ${ |
| 2256 | isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : "cursor-pointer" | 2256 | isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : "cursor-pointer" |
| 2257 | }`} | 2257 | }`} |
| 2258 | {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})} | 2258 | {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})} |
| ... | @@ -2292,7 +2292,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -2292,7 +2292,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2292 | )} | 2292 | )} |
| 2293 | <ProjectFavicon environmentId={project.environmentId} cwd={project.workspaceRoot} /> | 2293 | <ProjectFavicon environmentId={project.environmentId} cwd={project.workspaceRoot} /> |
| 2294 | <span className="flex min-w-0 flex-1 items-center gap-2"> | 2294 | <span className="flex min-w-0 flex-1 items-center gap-2"> |
| 2295 | <span className="truncate text-xs font-medium text-foreground/90"> | 2295 | <span className="truncate text-sm font-medium text-sidebar-foreground/90"> |
| 2296 | {project.displayName} | 2296 | {project.displayName} |
| 2297 | </span> | 2297 | </span> |
| 2298 | {project.groupedProjectCount > 1 ? ( | 2298 | {project.groupedProjectCount > 1 ? ( |
| ... | @@ -2899,13 +2899,13 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2899,13 +2899,13 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2899 | render={ | 2899 | render={ |
| 2900 | <SidebarMenuButton | 2900 | <SidebarMenuButton |
| 2901 | size="sm" | 2901 | size="sm" |
| 2902 | className="gap-2 px-2 py-1.5 text-muted-foreground/70 hover:bg-accent hover:text-foreground focus-visible:ring-0" | 2902 | className="h-8 gap-2 rounded-md px-2 py-1.5 text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-0" |
| 2903 | data-testid="command-palette-trigger" | 2903 | data-testid="command-palette-trigger" |
| 2904 | /> | 2904 | /> |
| 2905 | } | 2905 | } |
| 2906 | > | 2906 | > |
| 2907 | <SearchIcon className="size-3.5 text-muted-foreground/70" /> | 2907 | <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" /> |
| 2908 | <span className="flex-1 truncate text-left text-xs">Search</span> | 2908 | <span className="flex-1 truncate text-left text-sm font-medium">Search</span> |
| 2909 | {commandPaletteShortcutLabel ? ( | 2909 | {commandPaletteShortcutLabel ? ( |
| 2910 | <Kbd className="h-4 min-w-0 rounded-sm px-1.5 text-[10px]"> | 2910 | <Kbd className="h-4 min-w-0 rounded-sm px-1.5 text-[10px]"> |
| 2911 | {commandPaletteShortcutLabel} | 2911 | {commandPaletteShortcutLabel} |
| ... | @@ -2941,9 +2941,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2941,9 +2941,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2941 | <LocalSecondaryStatus /> | 2941 | <LocalSecondaryStatus /> |
| 2942 | <SidebarGroup className="px-2 py-2"> | 2942 | <SidebarGroup className="px-2 py-2"> |
| 2943 | <div className="mb-1 flex items-center justify-between pl-2 pr-1.5"> | 2943 | <div className="mb-1 flex items-center justify-between pl-2 pr-1.5"> |
| 2944 | <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60"> | 2944 | <span className="text-xs font-medium text-sidebar-muted-foreground/80">Projects</span> |
| 2945 | Projects | ||
| 2946 | </span> | ||
| 2947 | <div className="flex items-center gap-1"> | 2945 | <div className="flex items-center gap-1"> |
| 2948 | <ProjectSortMenu | 2946 | <ProjectSortMenu |
| 2949 | projectSortOrder={projectSortOrder} | 2947 | projectSortOrder={projectSortOrder} |
apps/web/src/components/SidebarStageBackdrop.test.tsx created+22| ... | @@ -0,0 +1,22 @@ | ||
| 1 | import { describe, expect, it } from "vite-plus/test"; | ||
| 2 | import { renderToStaticMarkup } from "react-dom/server"; | ||
| 3 | |||
| 4 | import { StageBackdropArt, StageBackdropButtonArt } from "./SidebarStageBackdrop"; | ||
| 5 | |||
| 6 | describe("SidebarStageBackdrop", () => { | ||
| 7 | it.each(["nightly", "dev"] as const)( | ||
| 8 | "uses unique SVG definition ids when %s artwork is rendered more than once", | ||
| 9 | (variant) => { | ||
| 10 | const markup = renderToStaticMarkup( | ||
| 11 | <> | ||
| 12 | <StageBackdropArt variant={variant} /> | ||
| 13 | <StageBackdropButtonArt variant={variant} /> | ||
| 14 | </>, | ||
| 15 | ); | ||
| 16 | const ids = Array.from(markup.matchAll(/\sid="([^"]+)"/g), (match) => match[1]); | ||
| 17 | |||
| 18 | expect(ids.length).toBeGreaterThan(0); | ||
| 19 | expect(new Set(ids).size).toBe(ids.length); | ||
| 20 | }, | ||
| 21 | ); | ||
| 22 | }); | ||
apps/web/src/components/SidebarStageBackdrop.tsx+60-50| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { useAtomValue } from "@effect/atom-react"; | 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import { useId } from "react"; | ||
| 2 | 3 | ||
| 3 | import { APP_STAGE_LABEL } from "../branding"; | 4 | import { APP_STAGE_LABEL } from "../branding"; |
| 4 | import { resolveServerBackedAppStageLabel } from "../branding.logic"; | 5 | import { resolveServerBackedAppStageLabel } from "../branding.logic"; |
| ... | @@ -47,6 +48,10 @@ export function StageBackdropArt({ variant }: { variant: SidebarStageBackdropVar | ... | @@ -47,6 +48,10 @@ export function StageBackdropArt({ variant }: { variant: SidebarStageBackdropVar |
| 47 | return variant === "nightly" ? <NightlySkyArt /> : <DevBlueprintArt />; | 48 | return variant === "nightly" ? <NightlySkyArt /> : <DevBlueprintArt />; |
| 48 | } | 49 | } |
| 49 | 50 | ||
| 51 | export function StageBackdropButtonArt({ variant }: { variant: SidebarStageBackdropVariant }) { | ||
| 52 | return variant === "nightly" ? <NightlySkyArt compact /> : <DevBlueprintArt compact />; | ||
| 53 | } | ||
| 54 | |||
| 50 | const NIGHTLY_STARS: ReadonlyArray<{ | 55 | const NIGHTLY_STARS: ReadonlyArray<{ |
| 51 | cx: number; | 56 | cx: number; |
| 52 | cy: number; | 57 | cy: number; |
| ... | @@ -78,18 +83,26 @@ const NIGHTLY_SPARKLES: ReadonlyArray<{ x: number; y: number }> = [ | ... | @@ -78,18 +83,26 @@ const NIGHTLY_SPARKLES: ReadonlyArray<{ x: number; y: number }> = [ |
| 78 | { x: 246, y: 26 }, | 83 | { x: 246, y: 26 }, |
| 79 | ]; | 84 | ]; |
| 80 | 85 | ||
| 81 | function NightlySkyArt() { | 86 | function NightlySkyArt({ compact = false }: { compact?: boolean }) { |
| 87 | const idPrefix = useId().replaceAll(":", ""); | ||
| 88 | const skyId = `${idPrefix}-stage-night-sky`; | ||
| 89 | const glowId = `${idPrefix}-stage-night-glow`; | ||
| 90 | const cloudId = `${idPrefix}-stage-night-cloud`; | ||
| 91 | const softId = `${idPrefix}-stage-night-soft`; | ||
| 92 | const starsId = `${idPrefix}-stage-night-stars`; | ||
| 93 | const glowsId = `${idPrefix}-stage-night-glows`; | ||
| 94 | |||
| 82 | return ( | 95 | return ( |
| 83 | <svg | 96 | <svg |
| 84 | className="h-full w-full" | 97 | className="h-full w-full" |
| 85 | fill="none" | 98 | fill="none" |
| 86 | preserveAspectRatio="xMinYMin slice" | 99 | preserveAspectRatio="xMinYMin slice" |
| 87 | viewBox={STAGE_BACKDROP_VIEW_BOX} | 100 | viewBox={compact ? "96 0 8192 96" : STAGE_BACKDROP_VIEW_BOX} |
| 88 | xmlns="http://www.w3.org/2000/svg" | 101 | xmlns="http://www.w3.org/2000/svg" |
| 89 | > | 102 | > |
| 90 | <defs> | 103 | <defs> |
| 91 | <linearGradient | 104 | <linearGradient |
| 92 | id="stage-night-sky" | 105 | id={skyId} |
| 93 | x1="24" | 106 | x1="24" |
| 94 | y1="0" | 107 | y1="0" |
| 95 | x2="264" | 108 | x2="264" |
| ... | @@ -102,7 +115,7 @@ function NightlySkyArt() { | ... | @@ -102,7 +115,7 @@ function NightlySkyArt() { |
| 102 | <stop offset="1" stopColor="#32155B" /> | 115 | <stop offset="1" stopColor="#32155B" /> |
| 103 | </linearGradient> | 116 | </linearGradient> |
| 104 | <radialGradient | 117 | <radialGradient |
| 105 | id="stage-night-glow" | 118 | id={glowId} |
| 106 | cx="0" | 119 | cx="0" |
| 107 | cy="0" | 120 | cy="0" |
| 108 | r="1" | 121 | r="1" |
| ... | @@ -113,29 +126,15 @@ function NightlySkyArt() { | ... | @@ -113,29 +126,15 @@ function NightlySkyArt() { |
| 113 | <stop offset="0.5" stopColor="#283075" stopOpacity="0.16" /> | 126 | <stop offset="0.5" stopColor="#283075" stopOpacity="0.16" /> |
| 114 | <stop offset="1" stopColor="#111635" stopOpacity="0" /> | 127 | <stop offset="1" stopColor="#111635" stopOpacity="0" /> |
| 115 | </radialGradient> | 128 | </radialGradient> |
| 116 | <linearGradient | 129 | <linearGradient id={cloudId} x1="0" y1="60" x2="288" y2="96" gradientUnits="userSpaceOnUse"> |
| 117 | id="stage-night-cloud" | ||
| 118 | x1="0" | ||
| 119 | y1="60" | ||
| 120 | x2="288" | ||
| 121 | y2="96" | ||
| 122 | gradientUnits="userSpaceOnUse" | ||
| 123 | > | ||
| 124 | <stop stopColor="#4EA4FF" stopOpacity="0.5" /> | 130 | <stop stopColor="#4EA4FF" stopOpacity="0.5" /> |
| 125 | <stop offset="0.52" stopColor="#696FEA" stopOpacity="0.62" /> | 131 | <stop offset="0.52" stopColor="#696FEA" stopOpacity="0.62" /> |
| 126 | <stop offset="1" stopColor="#A85BEA" stopOpacity="0.5" /> | 132 | <stop offset="1" stopColor="#A85BEA" stopOpacity="0.5" /> |
| 127 | </linearGradient> | 133 | </linearGradient> |
| 128 | <filter | 134 | <filter id={softId} x="-24" y="-24" width="336" height="144" filterUnits="userSpaceOnUse"> |
| 129 | id="stage-night-soft" | ||
| 130 | x="-24" | ||
| 131 | y="-24" | ||
| 132 | width="336" | ||
| 133 | height="144" | ||
| 134 | filterUnits="userSpaceOnUse" | ||
| 135 | > | ||
| 136 | <feGaussianBlur stdDeviation="4" /> | 135 | <feGaussianBlur stdDeviation="4" /> |
| 137 | </filter> | 136 | </filter> |
| 138 | <pattern id="stage-night-stars" width="288" height="96" patternUnits="userSpaceOnUse"> | 137 | <pattern id={starsId} width="288" height="96" patternUnits="userSpaceOnUse"> |
| 139 | <g fill="#E4EAFF"> | 138 | <g fill="#E4EAFF"> |
| 140 | {NIGHTLY_STARS.map((star) => ( | 139 | {NIGHTLY_STARS.map((star) => ( |
| 141 | <circle | 140 | <circle |
| ... | @@ -156,25 +155,25 @@ function NightlySkyArt() { | ... | @@ -156,25 +155,25 @@ function NightlySkyArt() { |
| 156 | ))} | 155 | ))} |
| 157 | </g> | 156 | </g> |
| 158 | </pattern> | 157 | </pattern> |
| 159 | <pattern id="stage-night-glows" width="640" height="96" patternUnits="userSpaceOnUse"> | 158 | <pattern id={glowsId} width="640" height="96" patternUnits="userSpaceOnUse"> |
| 160 | <rect width="640" height="96" fill="url(#stage-night-glow)" /> | 159 | <rect width="640" height="96" fill={`url(#${glowId})`} /> |
| 161 | </pattern> | 160 | </pattern> |
| 162 | </defs> | 161 | </defs> |
| 163 | 162 | ||
| 164 | <rect width="100%" height="96" fill="url(#stage-night-sky)" /> | 163 | <rect width="100%" height="96" fill={`url(#${skyId})`} /> |
| 165 | <rect width="100%" height="96" fill="url(#stage-night-glows)" /> | 164 | <rect width="100%" height="96" fill={`url(#${glowsId})`} /> |
| 166 | <rect width="100%" height="96" fill="url(#stage-night-stars)" /> | 165 | <rect width="100%" height="96" fill={`url(#${starsId})`} /> |
| 167 | 166 | ||
| 168 | <g filter="url(#stage-night-soft)"> | 167 | <g filter={`url(#${softId})`}> |
| 169 | <path | 168 | <path |
| 170 | d="M-12 88C-12 74 0 63 14 63C18 50 30 41 44 41C58 41 70 49 74 62C79 57 86 54 94 54C110 54 123 66 124 82C132 83 138 88 141 96H-12V88Z" | 169 | d="M-12 88C-12 74 0 63 14 63C18 50 30 41 44 41C58 41 70 49 74 62C79 57 86 54 94 54C110 54 123 66 124 82C132 83 138 88 141 96H-12V88Z" |
| 171 | fill="url(#stage-night-cloud)" | 170 | fill={`url(#${cloudId})`} |
| 172 | /> | 171 | /> |
| 173 | </g> | 172 | </g> |
| 174 | <g filter="url(#stage-night-soft)"> | 173 | <g filter={`url(#${softId})`}> |
| 175 | <path | 174 | <path |
| 176 | d="M150 96C151 84 161 75 173 75C176 64 186 57 198 57C210 57 220 64 223 75C231 75 238 80 241 87C250 87 257 91 260 96H150Z" | 175 | d="M150 96C151 84 161 75 173 75C176 64 186 57 198 57C210 57 220 64 223 75C231 75 238 80 241 87C250 87 257 91 260 96H150Z" |
| 177 | fill="url(#stage-night-cloud)" | 176 | fill={`url(#${cloudId})`} |
| 178 | fillOpacity="0.8" | 177 | fillOpacity="0.8" |
| 179 | /> | 178 | /> |
| 180 | </g> | 179 | </g> |
| ... | @@ -182,18 +181,29 @@ function NightlySkyArt() { | ... | @@ -182,18 +181,29 @@ function NightlySkyArt() { |
| 182 | ); | 181 | ); |
| 183 | } | 182 | } |
| 184 | 183 | ||
| 185 | function DevBlueprintArt() { | 184 | function DevBlueprintArt({ compact = false }: { compact?: boolean }) { |
| 185 | const idPrefix = useId().replaceAll(":", ""); | ||
| 186 | const paperId = `${idPrefix}-stage-bp-paper`; | ||
| 187 | const glowId = `${idPrefix}-stage-bp-glow`; | ||
| 188 | const celesteGlowId = `${idPrefix}-stage-bp-glow-celeste`; | ||
| 189 | const violetGlowId = `${idPrefix}-stage-bp-glow-violet`; | ||
| 190 | const minorGridId = `${idPrefix}-stage-bp-grid-minor`; | ||
| 191 | const majorGridId = `${idPrefix}-stage-bp-grid-major`; | ||
| 192 | const rulerId = `${idPrefix}-stage-bp-ruler`; | ||
| 193 | const glowsId = `${idPrefix}-stage-bp-glows`; | ||
| 194 | const annotationsId = `${idPrefix}-stage-bp-annotations`; | ||
| 195 | |||
| 186 | return ( | 196 | return ( |
| 187 | <svg | 197 | <svg |
| 188 | className="stage-blueprint h-full w-full" | 198 | className="stage-blueprint h-full w-full" |
| 189 | fill="none" | 199 | fill="none" |
| 190 | preserveAspectRatio="xMinYMin slice" | 200 | preserveAspectRatio="xMinYMin slice" |
| 191 | viewBox={STAGE_BACKDROP_VIEW_BOX} | 201 | viewBox={compact ? "64 0 8192 96" : STAGE_BACKDROP_VIEW_BOX} |
| 192 | xmlns="http://www.w3.org/2000/svg" | 202 | xmlns="http://www.w3.org/2000/svg" |
| 193 | > | 203 | > |
| 194 | <defs> | 204 | <defs> |
| 195 | <linearGradient | 205 | <linearGradient |
| 196 | id="stage-bp-paper" | 206 | id={paperId} |
| 197 | x1="60" | 207 | x1="60" |
| 198 | y1="0" | 208 | y1="0" |
| 199 | x2="220" | 209 | x2="220" |
| ... | @@ -206,7 +216,7 @@ function DevBlueprintArt() { | ... | @@ -206,7 +216,7 @@ function DevBlueprintArt() { |
| 206 | <stop offset="1" style={{ stopColor: "var(--stage-bp-top)" }} /> | 216 | <stop offset="1" style={{ stopColor: "var(--stage-bp-top)" }} /> |
| 207 | </linearGradient> | 217 | </linearGradient> |
| 208 | <radialGradient | 218 | <radialGradient |
| 209 | id="stage-bp-glow" | 219 | id={glowId} |
| 210 | cx="0" | 220 | cx="0" |
| 211 | cy="0" | 221 | cy="0" |
| 212 | r="1" | 222 | r="1" |
| ... | @@ -218,7 +228,7 @@ function DevBlueprintArt() { | ... | @@ -218,7 +228,7 @@ function DevBlueprintArt() { |
| 218 | <stop offset="1" stopColor="#276AF1" stopOpacity="0" /> | 228 | <stop offset="1" stopColor="#276AF1" stopOpacity="0" /> |
| 219 | </radialGradient> | 229 | </radialGradient> |
| 220 | <radialGradient | 230 | <radialGradient |
| 221 | id="stage-bp-glow-celeste" | 231 | id={celesteGlowId} |
| 222 | cx="0" | 232 | cx="0" |
| 223 | cy="0" | 233 | cy="0" |
| 224 | r="1" | 234 | r="1" |
| ... | @@ -230,7 +240,7 @@ function DevBlueprintArt() { | ... | @@ -230,7 +240,7 @@ function DevBlueprintArt() { |
| 230 | <stop offset="1" stopColor="#277EF1" stopOpacity="0" /> | 240 | <stop offset="1" stopColor="#277EF1" stopOpacity="0" /> |
| 231 | </radialGradient> | 241 | </radialGradient> |
| 232 | <radialGradient | 242 | <radialGradient |
| 233 | id="stage-bp-glow-violet" | 243 | id={violetGlowId} |
| 234 | cx="0" | 244 | cx="0" |
| 235 | cy="0" | 245 | cy="0" |
| 236 | r="1" | 246 | r="1" |
| ... | @@ -241,13 +251,13 @@ function DevBlueprintArt() { | ... | @@ -241,13 +251,13 @@ function DevBlueprintArt() { |
| 241 | <stop offset="0.52" stopColor="#7C8BFF" stopOpacity="0.14" /> | 251 | <stop offset="0.52" stopColor="#7C8BFF" stopOpacity="0.14" /> |
| 242 | <stop offset="1" stopColor="#3155DF" stopOpacity="0" /> | 252 | <stop offset="1" stopColor="#3155DF" stopOpacity="0" /> |
| 243 | </radialGradient> | 253 | </radialGradient> |
| 244 | <pattern id="stage-bp-grid-minor" width="8" height="8" patternUnits="userSpaceOnUse"> | 254 | <pattern id={minorGridId} width="8" height="8" patternUnits="userSpaceOnUse"> |
| 245 | <path d="M8 0H0V8" stroke="#EAF6FF" strokeOpacity="0.14" strokeWidth="0.5" /> | 255 | <path d="M8 0H0V8" stroke="#EAF6FF" strokeOpacity="0.14" strokeWidth="0.5" /> |
| 246 | </pattern> | 256 | </pattern> |
| 247 | <pattern id="stage-bp-grid-major" width="32" height="32" patternUnits="userSpaceOnUse"> | 257 | <pattern id={majorGridId} width="32" height="32" patternUnits="userSpaceOnUse"> |
| 248 | <path d="M32 0H0V32" stroke="#EAF6FF" strokeOpacity="0.26" strokeWidth="0.6" /> | 258 | <path d="M32 0H0V32" stroke="#EAF6FF" strokeOpacity="0.26" strokeWidth="0.6" /> |
| 249 | </pattern> | 259 | </pattern> |
| 250 | <pattern id="stage-bp-ruler" width="32" height="6" patternUnits="userSpaceOnUse"> | 260 | <pattern id={rulerId} width="32" height="6" patternUnits="userSpaceOnUse"> |
| 251 | <path | 261 | <path |
| 252 | d="M4 0V2.5M12 0V2.5M20 0V4M28 0V2.5" | 262 | d="M4 0V2.5M12 0V2.5M20 0V4M28 0V2.5" |
| 253 | stroke="#DDF7FF" | 263 | stroke="#DDF7FF" |
| ... | @@ -255,12 +265,12 @@ function DevBlueprintArt() { | ... | @@ -255,12 +265,12 @@ function DevBlueprintArt() { |
| 255 | strokeWidth="0.5" | 265 | strokeWidth="0.5" |
| 256 | /> | 266 | /> |
| 257 | </pattern> | 267 | </pattern> |
| 258 | <pattern id="stage-bp-glows" width="768" height="96" patternUnits="userSpaceOnUse"> | 268 | <pattern id={glowsId} width="768" height="96" patternUnits="userSpaceOnUse"> |
| 259 | <rect width="768" height="96" fill="url(#stage-bp-glow)" /> | 269 | <rect width="768" height="96" fill={`url(#${glowId})`} /> |
| 260 | <rect width="768" height="96" fill="url(#stage-bp-glow-celeste)" /> | 270 | <rect width="768" height="96" fill={`url(#${celesteGlowId})`} /> |
| 261 | <rect width="768" height="96" fill="url(#stage-bp-glow-violet)" /> | 271 | <rect width="768" height="96" fill={`url(#${violetGlowId})`} /> |
| 262 | </pattern> | 272 | </pattern> |
| 263 | <pattern id="stage-bp-annotations" width="768" height="96" patternUnits="userSpaceOnUse"> | 273 | <pattern id={annotationsId} width="768" height="96" patternUnits="userSpaceOnUse"> |
| 264 | <g stroke="#DDF7FF" strokeLinecap="round" strokeOpacity="0.6" strokeWidth="0.7"> | 274 | <g stroke="#DDF7FF" strokeLinecap="round" strokeOpacity="0.6" strokeWidth="0.7"> |
| 265 | <path d="M180 64H264" strokeDasharray="5 4" /> | 275 | <path d="M180 64H264" strokeDasharray="5 4" /> |
| 266 | <path d="M180 61V67M264 61V67" /> | 276 | <path d="M180 61V67M264 61V67" /> |
| ... | @@ -309,12 +319,12 @@ function DevBlueprintArt() { | ... | @@ -309,12 +319,12 @@ function DevBlueprintArt() { |
| 309 | </pattern> | 319 | </pattern> |
| 310 | </defs> | 320 | </defs> |
| 311 | 321 | ||
| 312 | <rect width="100%" height="96" fill="url(#stage-bp-paper)" /> | 322 | <rect width="100%" height="96" fill={`url(#${paperId})`} /> |
| 313 | <rect width="100%" height="96" fill="url(#stage-bp-glows)" /> | 323 | <rect width="100%" height="96" fill={`url(#${glowsId})`} /> |
| 314 | <rect width="100%" height="96" fill="url(#stage-bp-grid-minor)" /> | 324 | <rect width="100%" height="96" fill={`url(#${minorGridId})`} /> |
| 315 | <rect width="100%" height="96" fill="url(#stage-bp-grid-major)" /> | 325 | <rect width="100%" height="96" fill={`url(#${majorGridId})`} /> |
| 316 | <rect width="100%" height="6" fill="url(#stage-bp-ruler)" /> | 326 | <rect width="100%" height="6" fill={`url(#${rulerId})`} /> |
| 317 | <rect width="100%" height="96" fill="url(#stage-bp-annotations)" /> | 327 | <rect width="100%" height="96" fill={`url(#${annotationsId})`} /> |
| 318 | </svg> | 328 | </svg> |
| 319 | ); | 329 | ); |
| 320 | } | 330 | } |
apps/web/src/components/SidebarV2.tsx+120-68| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | import { autoAnimate } from "@formkit/auto-animate"; | 1 | import { autoAnimate } from "@formkit/auto-animate"; |
| 2 | import { useAtomValue } from "@effect/atom-react"; | 2 | import { useAtomValue } from "@effect/atom-react"; |
| 3 | import { effectiveSettled } from "@t3tools/client-runtime/state/thread-settled"; | 3 | import { effectiveSettled } from "@t3tools/client-runtime/state/thread-settled"; |
| 4 | import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/models"; | 4 | import type { |
| 5 | EnvironmentProject, | ||
| 6 | EnvironmentThreadShell, | ||
| 7 | } from "@t3tools/client-runtime/state/models"; | ||
| 5 | import { | 8 | import { |
| 6 | scopeProjectRef, | 9 | scopeProjectRef, |
| 7 | scopeThreadRef, | 10 | scopeThreadRef, |
| ... | @@ -11,9 +14,9 @@ import type { ScopedThreadRef } from "@t3tools/contracts"; | ... | @@ -11,9 +14,9 @@ import type { ScopedThreadRef } from "@t3tools/contracts"; |
| 11 | import { | 14 | import { |
| 12 | CheckIcon, | 15 | CheckIcon, |
| 13 | ChevronDownIcon, | 16 | ChevronDownIcon, |
| 17 | CircleAlertIcon, | ||
| 14 | CircleCheckIcon, | 18 | CircleCheckIcon, |
| 15 | CircleDashedIcon, | 19 | CircleDashedIcon, |
| 16 | CircleAlertIcon, | ||
| 17 | FolderIcon, | 20 | FolderIcon, |
| 18 | FolderPlusIcon, | 21 | FolderPlusIcon, |
| 19 | GitBranchIcon, | 22 | GitBranchIcon, |
| ... | @@ -22,6 +25,7 @@ import { | ... | @@ -22,6 +25,7 @@ import { |
| 22 | SearchIcon, | 25 | SearchIcon, |
| 23 | ServerIcon, | 26 | ServerIcon, |
| 24 | SquarePenIcon, | 27 | SquarePenIcon, |
| 28 | Trash2Icon, | ||
| 25 | Undo2Icon, | 29 | Undo2Icon, |
| 26 | } from "lucide-react"; | 30 | } from "lucide-react"; |
| 27 | import { | 31 | import { |
| ... | @@ -69,6 +73,7 @@ import { useProjects, useThreadShells } from "../state/entities"; | ... | @@ -69,6 +73,7 @@ import { useProjects, useThreadShells } from "../state/entities"; |
| 69 | import { environmentServerConfigsAtom, primaryServerKeybindingsAtom } from "../state/server"; | 73 | import { environmentServerConfigsAtom, primaryServerKeybindingsAtom } from "../state/server"; |
| 70 | import { vcsEnvironment } from "../state/vcs"; | 74 | import { vcsEnvironment } from "../state/vcs"; |
| 71 | import { threadEnvironment } from "../state/threads"; | 75 | import { threadEnvironment } from "../state/threads"; |
| 76 | import { projectEnvironment } from "../state/projects"; | ||
| 72 | import { useEnvironmentQuery } from "../state/query"; | 77 | import { useEnvironmentQuery } from "../state/query"; |
| 73 | import { useAtomCommand } from "../state/use-atom-command"; | 78 | import { useAtomCommand } from "../state/use-atom-command"; |
| 74 | import { buildThreadRouteParams, resolveThreadRouteRef } from "../threadRoutes"; | 79 | import { buildThreadRouteParams, resolveThreadRouteRef } from "../threadRoutes"; |
| ... | @@ -94,15 +99,10 @@ import { stackedThreadToast, toastManager } from "./ui/toast"; | ... | @@ -94,15 +99,10 @@ import { stackedThreadToast, toastManager } from "./ui/toast"; |
| 94 | import { CommandDialogTrigger } from "./ui/command"; | 99 | import { CommandDialogTrigger } from "./ui/command"; |
| 95 | import { Kbd } from "./ui/kbd"; | 100 | import { Kbd } from "./ui/kbd"; |
| 96 | import { Menu, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuTrigger } from "./ui/menu"; | 101 | import { Menu, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuTrigger } from "./ui/menu"; |
| 97 | import { | 102 | import { SidebarContent, SidebarGroup, SidebarMenuButton, useSidebar } from "./ui/sidebar"; |
| 98 | SidebarContent, | ||
| 99 | SidebarGroup, | ||
| 100 | SidebarMenuButton, | ||
| 101 | SidebarSeparator, | ||
| 102 | useSidebar, | ||
| 103 | } from "./ui/sidebar"; | ||
| 104 | import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrome"; | 103 | import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrome"; |
| 105 | import { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from "./ui/tooltip"; | 104 | import { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from "./ui/tooltip"; |
| 105 | import { useComposerDraftStore } from "../composerDraftStore"; | ||
| 106 | 106 | ||
| 107 | // Settled-tail paging: recent history is the common lookup; the deep tail | 107 | // Settled-tail paging: recent history is the common lookup; the deep tail |
| 108 | // stays behind an explicit Show more. | 108 | // stays behind an explicit Show more. |
| ... | @@ -127,7 +127,6 @@ function SidebarV2ThreadTooltip({ | ... | @@ -127,7 +127,6 @@ function SidebarV2ThreadTooltip({ |
| 127 | driverKind, | 127 | driverKind, |
| 128 | modelInstanceId, | 128 | modelInstanceId, |
| 129 | modelLabel, | 129 | modelLabel, |
| 130 | status, | ||
| 131 | branchMismatch, | 130 | branchMismatch, |
| 132 | }: { | 131 | }: { |
| 133 | thread: SidebarThreadSummary; | 132 | thread: SidebarThreadSummary; |
| ... | @@ -137,11 +136,6 @@ function SidebarV2ThreadTooltip({ | ... | @@ -137,11 +136,6 @@ function SidebarV2ThreadTooltip({ |
| 137 | driverKind: ProviderInstanceEntry["driverKind"] | null; | 136 | driverKind: ProviderInstanceEntry["driverKind"] | null; |
| 138 | modelInstanceId: string; | 137 | modelInstanceId: string; |
| 139 | modelLabel: string; | 138 | modelLabel: string; |
| 140 | status: { | ||
| 141 | label: string; | ||
| 142 | className: string; | ||
| 143 | icon: "working" | "done" | null; | ||
| 144 | } | null; | ||
| 145 | branchMismatch: { | 139 | branchMismatch: { |
| 146 | threadBranch: string; | 140 | threadBranch: string; |
| 147 | currentBranch: string; | 141 | currentBranch: string; |
| ... | @@ -152,58 +146,31 @@ function SidebarV2ThreadTooltip({ | ... | @@ -152,58 +146,31 @@ function SidebarV2ThreadTooltip({ |
| 152 | side="right" | 146 | side="right" |
| 153 | align="start" | 147 | align="start" |
| 154 | sideOffset={8} | 148 | sideOffset={8} |
| 155 | className="w-80 text-left whitespace-normal shadow-lg/10 dark:border-transparent dark:shadow-none dark:inset-ring-1 dark:inset-ring-white/5" | 149 | className="dropdown-glass max-w-80 border-0! bg-secondary! text-left whitespace-normal shadow-lg/10 before:hidden dark:shadow-none" |
| 156 | > | 150 | > |
| 157 | <div className="flex w-full flex-col gap-3 p-2"> | 151 | <div className="flex max-w-80 flex-col gap-2 p-2"> |
| 158 | <div className="flex min-w-0 items-start gap-3"> | 152 | <div className="whitespace-nowrap text-sm font-medium text-foreground">{thread.title}</div> |
| 159 | <div className="min-w-0 flex-1 text-sm font-medium text-foreground">{thread.title}</div> | 153 | <div className="grid gap-1.5 text-xs text-muted-foreground"> |
| 160 | {status ? ( | ||
| 161 | <div | ||
| 162 | className={cn( | ||
| 163 | "inline-flex shrink-0 items-center gap-1 text-xs font-medium", | ||
| 164 | status.className, | ||
| 165 | )} | ||
| 166 | > | ||
| 167 | {status.icon === "working" ? ( | ||
| 168 | <CircleDashedIcon aria-hidden className="size-4 shrink-0" /> | ||
| 169 | ) : status.icon === "done" ? ( | ||
| 170 | <CircleCheckIcon aria-hidden className="size-4 shrink-0" /> | ||
| 171 | ) : null} | ||
| 172 | {status.label} | ||
| 173 | </div> | ||
| 174 | ) : ( | ||
| 175 | <div className="shrink-0 tabular-nums text-xs text-muted-foreground"> | ||
| 176 | {threadTimeLabel(thread)} | ||
| 177 | </div> | ||
| 178 | )} | ||
| 179 | </div> | ||
| 180 | <div className="grid gap-2 text-xs text-muted-foreground"> | ||
| 181 | {projectTitle ? ( | 154 | {projectTitle ? ( |
| 182 | <div className="flex min-w-0 items-start gap-2"> | 155 | <div className="flex min-w-0 items-center gap-2"> |
| 183 | <ProjectFavicon | 156 | <ProjectFavicon |
| 184 | environmentId={thread.environmentId} | 157 | environmentId={thread.environmentId} |
| 185 | cwd={projectCwd ?? ""} | 158 | cwd={projectCwd ?? ""} |
| 186 | className="size-4 shrink-0" | 159 | className="size-4 shrink-0" |
| 187 | /> | 160 | /> |
| 188 | <div className="min-w-0 flex-1 wrap-break-word text-foreground/90"> | 161 | <div className="min-w-0 wrap-break-word text-foreground/90">{projectTitle}</div> |
| 189 | {projectTitle} | ||
| 190 | </div> | ||
| 191 | </div> | 162 | </div> |
| 192 | ) : null} | 163 | ) : null} |
| 193 | {environmentLabel ? ( | 164 | {environmentLabel ? ( |
| 194 | <div className="flex min-w-0 items-start gap-2"> | 165 | <div className="flex min-w-0 items-center gap-2"> |
| 195 | <ServerIcon className="size-4 shrink-0 stroke-muted-foreground" /> | 166 | <ServerIcon className="size-4 shrink-0 stroke-muted-foreground" /> |
| 196 | <div className="min-w-0 flex-1 wrap-break-word text-foreground/90"> | 167 | <div className="min-w-0 wrap-break-word text-foreground/90">{environmentLabel}</div> |
| 197 | {environmentLabel} | ||
| 198 | </div> | ||
| 199 | </div> | 168 | </div> |
| 200 | ) : null} | 169 | ) : null} |
| 201 | {thread.branch ? ( | 170 | {thread.branch ? ( |
| 202 | <div className="flex min-w-0 items-start gap-2"> | 171 | <div className="flex min-w-0 items-center gap-2"> |
| 203 | <GitBranchIcon className="size-4 shrink-0 stroke-muted-foreground" /> | 172 | <GitBranchIcon className="size-4 shrink-0 stroke-muted-foreground" /> |
| 204 | <div className="min-w-0 flex-1 wrap-break-word text-foreground/90"> | 173 | <div className="min-w-0 wrap-break-word text-foreground/90">{thread.branch}</div> |
| 205 | {thread.branch} | ||
| 206 | </div> | ||
| 207 | </div> | 174 | </div> |
| 208 | ) : null} | 175 | ) : null} |
| 209 | {branchMismatch ? ( | 176 | {branchMismatch ? ( |
| ... | @@ -215,19 +182,19 @@ function SidebarV2ThreadTooltip({ | ... | @@ -215,19 +182,19 @@ function SidebarV2ThreadTooltip({ |
| 215 | </div> | 182 | </div> |
| 216 | ) : null} | 183 | ) : null} |
| 217 | {driverKind ? ( | 184 | {driverKind ? ( |
| 218 | <div className="flex min-w-0 items-start gap-2"> | 185 | <div className="flex min-w-0 items-center gap-2"> |
| 219 | <ProviderInstanceIcon | 186 | <ProviderInstanceIcon |
| 220 | driverKind={driverKind} | 187 | driverKind={driverKind} |
| 221 | displayName={thread.session?.providerName ?? modelInstanceId} | 188 | displayName={thread.session?.providerName ?? modelInstanceId} |
| 222 | iconClassName="size-4 shrink-0" | 189 | iconClassName="size-4 shrink-0" |
| 223 | /> | 190 | /> |
| 224 | <div className="min-w-0 flex-1 wrap-break-word text-foreground/90">{modelLabel}</div> | 191 | <div className="min-w-0 wrap-break-word text-foreground/90">{modelLabel}</div> |
| 225 | </div> | 192 | </div> |
| 226 | ) : null} | 193 | ) : null} |
| 227 | {thread.session?.lastError ? ( | 194 | {thread.session?.lastError ? ( |
| 228 | <div className="flex min-w-0 items-start gap-2 text-red-600 dark:text-red-400"> | 195 | <div className="flex min-w-0 items-center gap-2 text-red-600 dark:text-red-400"> |
| 229 | <CircleAlertIcon className="size-4 shrink-0 stroke-current" /> | 196 | <CircleAlertIcon className="size-4 shrink-0 stroke-current" /> |
| 230 | <div className="min-w-0 flex-1 wrap-break-word">{thread.session.lastError}</div> | 197 | <div className="min-w-0 wrap-break-word">{thread.session.lastError}</div> |
| 231 | </div> | 198 | </div> |
| 232 | ) : null} | 199 | ) : null} |
| 233 | </div> | 200 | </div> |
| ... | @@ -383,7 +350,6 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { | ... | @@ -383,7 +350,6 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { |
| 383 | driverKind={driverKind} | 350 | driverKind={driverKind} |
| 384 | modelInstanceId={modelInstanceId} | 351 | modelInstanceId={modelInstanceId} |
| 385 | modelLabel={modelLabel} | 352 | modelLabel={modelLabel} |
| 386 | status={topStatus} | ||
| 387 | branchMismatch={branchMismatch} | 353 | branchMismatch={branchMismatch} |
| 388 | /> | 354 | /> |
| 389 | ); | 355 | ); |
| ... | @@ -474,10 +440,11 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { | ... | @@ -474,10 +440,11 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { |
| 474 | // content; surface is reserved for interaction (hover, multi-select, route). | 440 | // content; surface is reserved for interaction (hover, multi-select, route). |
| 475 | const rowSurfaceClassName = cn( | 441 | const rowSurfaceClassName = cn( |
| 476 | "group/v2-row relative w-full cursor-pointer overflow-hidden rounded-md text-left outline-none select-none", | 442 | "group/v2-row relative w-full cursor-pointer overflow-hidden rounded-md text-left outline-none select-none", |
| 443 | variant === "card" && "backdrop-blur-[16px]", | ||
| 477 | props.isActive | 444 | props.isActive |
| 478 | ? "bg-sidebar-row-active text-sidebar-foreground dark:inset-ring-1 dark:inset-ring-white/5" | 445 | ? "bg-sidebar-row-active text-sidebar-foreground" |
| 479 | : isSelected | 446 | : isSelected |
| 480 | ? "bg-sidebar-row-selected text-sidebar-foreground dark:inset-ring-1 dark:inset-ring-white/5" | 447 | ? "bg-sidebar-row-selected text-sidebar-foreground" |
| 481 | : shouldRecede | 448 | : shouldRecede |
| 482 | ? "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" | 449 | ? "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 483 | : "bg-transparent text-sidebar-foreground hover:bg-sidebar-row-hover", | 450 | : "bg-transparent text-sidebar-foreground hover:bg-sidebar-row-hover", |
| ... | @@ -758,6 +725,9 @@ export default function SidebarV2() { | ... | @@ -758,6 +725,9 @@ export default function SidebarV2() { |
| 758 | const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { | 725 | const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { |
| 759 | reportFailure: false, | 726 | reportFailure: false, |
| 760 | }); | 727 | }); |
| 728 | const deleteProject = useAtomCommand(projectEnvironment.delete, { | ||
| 729 | reportFailure: false, | ||
| 730 | }); | ||
| 761 | const newThreadContext = useHandleNewThread(); | 731 | const newThreadContext = useHandleNewThread(); |
| 762 | const openAddProjectCommandPalette = useCallback( | 732 | const openAddProjectCommandPalette = useCallback( |
| 763 | () => openCommandPalette({ open: "add-project" }), | 733 | () => openCommandPalette({ open: "add-project" }), |
| ... | @@ -872,6 +842,77 @@ export default function SidebarV2() { | ... | @@ -872,6 +842,77 @@ export default function SidebarV2() { |
| 872 | clearSelection(); | 842 | clearSelection(); |
| 873 | }, [clearSelection, projectScopeKey]); | 843 | }, [clearSelection, projectScopeKey]); |
| 874 | 844 | ||
| 845 | const handleRemoveProject = useCallback( | ||
| 846 | async (project: EnvironmentProject) => { | ||
| 847 | const api = readLocalApi(); | ||
| 848 | if (!api) return; | ||
| 849 | |||
| 850 | const projectThreads = threads.filter( | ||
| 851 | (thread) => | ||
| 852 | thread.environmentId === project.environmentId && thread.projectId === project.id, | ||
| 853 | ); | ||
| 854 | const confirmed = await settlePromise(() => | ||
| 855 | api.dialogs.confirm( | ||
| 856 | projectThreads.length > 0 | ||
| 857 | ? [ | ||
| 858 | `Remove project "${project.title}" and delete its ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"}?`, | ||
| 859 | `Path: ${project.workspaceRoot}`, | ||
| 860 | "This permanently clears conversation history for those threads.", | ||
| 861 | "This removes only the project entry, not the files on disk.", | ||
| 862 | "This action cannot be undone.", | ||
| 863 | ].join("\n") | ||
| 864 | : [ | ||
| 865 | `Remove project "${project.title}"?`, | ||
| 866 | `Path: ${project.workspaceRoot}`, | ||
| 867 | "This removes only the project entry, not the files on disk.", | ||
| 868 | ].join("\n"), | ||
| 869 | ), | ||
| 870 | ); | ||
| 871 | if (confirmed._tag === "Failure" || !confirmed.value) return; | ||
| 872 | |||
| 873 | const projectRef = scopeProjectRef(project.environmentId, project.id); | ||
| 874 | const result = await deleteProject({ | ||
| 875 | environmentId: project.environmentId, | ||
| 876 | input: { | ||
| 877 | projectId: project.id, | ||
| 878 | ...(projectThreads.length > 0 ? { force: true } : {}), | ||
| 879 | }, | ||
| 880 | }); | ||
| 881 | if (result._tag === "Failure") { | ||
| 882 | if (!isAtomCommandInterrupted(result)) { | ||
| 883 | const error = squashAtomCommandFailure(result); | ||
| 884 | toastManager.add( | ||
| 885 | stackedThreadToast({ | ||
| 886 | type: "error", | ||
| 887 | title: `Failed to remove "${project.title}"`, | ||
| 888 | description: error instanceof Error ? error.message : "An error occurred.", | ||
| 889 | }), | ||
| 890 | ); | ||
| 891 | } | ||
| 892 | return; | ||
| 893 | } | ||
| 894 | |||
| 895 | const draftStore = useComposerDraftStore.getState(); | ||
| 896 | const projectDraftThread = draftStore.getDraftThreadByProjectRef(projectRef); | ||
| 897 | if (projectDraftThread) { | ||
| 898 | draftStore.clearDraftThread(projectDraftThread.draftId); | ||
| 899 | } | ||
| 900 | draftStore.clearProjectDraftThreadId(projectRef); | ||
| 901 | |||
| 902 | if ( | ||
| 903 | routeThreadRef && | ||
| 904 | projectThreads.some( | ||
| 905 | (thread) => | ||
| 906 | thread.environmentId === routeThreadRef.environmentId && | ||
| 907 | thread.id === routeThreadRef.threadId, | ||
| 908 | ) | ||
| 909 | ) { | ||
| 910 | void router.navigate({ to: "/" }); | ||
| 911 | } | ||
| 912 | }, | ||
| 913 | [deleteProject, routeThreadRef, router, threads], | ||
| 914 | ); | ||
| 915 | |||
| 875 | // Settled threads stay in the live shell stream (settled ≠ archived), so | 916 | // Settled threads stay in the live shell stream (settled ≠ archived), so |
| 876 | // the partition works directly off live shells: no archived-snapshot | 917 | // the partition works directly off live shells: no archived-snapshot |
| 877 | // merging, no optimistic holds. Archived threads remain hidden here — | 918 | // merging, no optimistic holds. Archived threads remain hidden here — |
| ... | @@ -1542,10 +1583,10 @@ export default function SidebarV2() { | ... | @@ -1542,10 +1583,10 @@ export default function SidebarV2() { |
| 1542 | <MenuRadioItem | 1583 | <MenuRadioItem |
| 1543 | value="all" | 1584 | value="all" |
| 1544 | closeOnClick | 1585 | closeOnClick |
| 1545 | className="[&>span:last-child]:flex [&>span:last-child]:min-w-0 [&>span:last-child]:items-center [&>span:last-child]:gap-2" | 1586 | className="h-8 min-h-8 px-1 py-0 text-sm font-medium [&>span:last-child]:flex [&>span:last-child]:min-w-0 [&>span:last-child]:items-center [&>span:last-child]:gap-2" |
| 1546 | > | 1587 | > |
| 1547 | <FolderIcon className="size-3.5" /> | 1588 | <FolderIcon className="size-4 shrink-0" /> |
| 1548 | <span className="min-w-0 truncate">All projects</span> | 1589 | <span className="min-w-0 truncate text-sm">All projects</span> |
| 1549 | </MenuRadioItem> | 1590 | </MenuRadioItem> |
| 1550 | {projects.map((project) => { | 1591 | {projects.map((project) => { |
| 1551 | const scopeKey = `${project.environmentId}:${project.id}`; | 1592 | const scopeKey = `${project.environmentId}:${project.id}`; |
| ... | @@ -1554,14 +1595,28 @@ export default function SidebarV2() { | ... | @@ -1554,14 +1595,28 @@ export default function SidebarV2() { |
| 1554 | key={scopeKey} | 1595 | key={scopeKey} |
| 1555 | value={scopeKey} | 1596 | value={scopeKey} |
| 1556 | closeOnClick | 1597 | closeOnClick |
| 1557 | className="[&>span:last-child]:flex [&>span:last-child]:min-w-0 [&>span:last-child]:items-center [&>span:last-child]:gap-2" | 1598 | className="h-8 min-h-8 px-1 py-0 text-sm font-medium [&>span:last-child]:flex [&>span:last-child]:min-w-0 [&>span:last-child]:items-center [&>span:last-child]:gap-2" |
| 1558 | > | 1599 | > |
| 1559 | <ProjectFavicon | 1600 | <ProjectFavicon |
| 1560 | environmentId={project.environmentId} | 1601 | environmentId={project.environmentId} |
| 1561 | cwd={project.workspaceRoot} | 1602 | cwd={project.workspaceRoot} |
| 1562 | className="size-3.5" | 1603 | className="size-4 shrink-0" |
| 1563 | /> | 1604 | /> |
| 1564 | <span className="min-w-0 truncate">{project.title}</span> | 1605 | <span className="min-w-0 truncate text-sm">{project.title}</span> |
| 1606 | <button | ||
| 1607 | type="button" | ||
| 1608 | aria-label={`Remove project ${project.title}`} | ||
| 1609 | title={`Remove ${project.title}`} | ||
| 1610 | className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/55 outline-none transition-colors hover:bg-destructive/12 hover:text-destructive focus-visible:bg-destructive/12 focus-visible:text-destructive focus-visible:ring-2 focus-visible:ring-destructive/40" | ||
| 1611 | onPointerDown={(event) => event.stopPropagation()} | ||
| 1612 | onClick={(event) => { | ||
| 1613 | event.preventDefault(); | ||
| 1614 | event.stopPropagation(); | ||
| 1615 | void handleRemoveProject(project); | ||
| 1616 | }} | ||
| 1617 | > | ||
| 1618 | <Trash2Icon className="size-3.5" /> | ||
| 1619 | </button> | ||
| 1565 | </MenuRadioItem> | 1620 | </MenuRadioItem> |
| 1566 | ); | 1621 | ); |
| 1567 | })} | 1622 | })} |
| ... | @@ -1718,9 +1773,6 @@ export default function SidebarV2() { | ... | @@ -1718,9 +1773,6 @@ export default function SidebarV2() { |
| 1718 | ) : null} | 1773 | ) : null} |
| 1719 | </SidebarGroup> | 1774 | </SidebarGroup> |
| 1720 | </SidebarContent> | 1775 | </SidebarContent> |
| 1721 | <div className="shrink-0 px-2"> | ||
| 1722 | <SidebarSeparator className="mx-0 w-full bg-sidebar-border/80" /> | ||
| 1723 | </div> | ||
| 1724 | <SidebarChromeFooter /> | 1776 | <SidebarChromeFooter /> |
| 1725 | </> | 1777 | </> |
| 1726 | ); | 1778 | ); |
apps/web/src/components/chat/ChangedFilesTree.tsx+1-1| ... | @@ -41,7 +41,7 @@ export const ChangedFilesCard = memo(function ChangedFilesCard(props: { | ... | @@ -41,7 +41,7 @@ export const ChangedFilesCard = memo(function ChangedFilesCard(props: { |
| 41 | const summaryStat = useMemo(() => summarizeTurnDiffStats(files), [files]); | 41 | const summaryStat = useMemo(() => summarizeTurnDiffStats(files), [files]); |
| 42 | 42 | ||
| 43 | return ( | 43 | return ( |
| 44 | <div className="mt-4 rounded-2xl border border-input bg-background p-2 pt-4 shadow-xs/5 not-dark:bg-clip-padding dark:bg-input/32"> | 44 | <div className="mt-4 rounded-2xl bg-background p-2 pt-4 shadow-xs/5 not-dark:bg-clip-padding dark:bg-input/32"> |
| 45 | <div className="sticky top-0 z-10 mb-3 flex items-center justify-between gap-2 bg-background px-2 before:absolute before:inset-x-0 before:-top-4 before:h-4 before:bg-background before:content-[''] dark:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))] dark:before:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))]"> | 45 | <div className="sticky top-0 z-10 mb-3 flex items-center justify-between gap-2 bg-background px-2 before:absolute before:inset-x-0 before:-top-4 before:h-4 before:bg-background before:content-[''] dark:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))] dark:before:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))]"> |
| 46 | <p className="flex items-center gap-1 whitespace-nowrap font-medium text-foreground text-xs leading-4"> | 46 | <p className="flex items-center gap-1 whitespace-nowrap font-medium text-foreground text-xs leading-4"> |
| 47 | <span> | 47 | <span> |
apps/web/src/components/chat/ChatComposer.tsx+2-12| ... | @@ -99,7 +99,6 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | ... | @@ -99,7 +99,6 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 99 | import { toastManager } from "../ui/toast"; | 99 | import { toastManager } from "../ui/toast"; |
| 100 | import { | 100 | import { |
| 101 | BotIcon, | 101 | BotIcon, |
| 102 | CheckIcon, | ||
| 103 | CircleAlertIcon, | 102 | CircleAlertIcon, |
| 104 | ListTodoIcon, | 103 | ListTodoIcon, |
| 105 | PencilRulerIcon, | 104 | PencilRulerIcon, |
| ... | @@ -280,7 +279,6 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop | ... | @@ -280,7 +279,6 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop |
| 280 | {runtimeModeOptions.map((mode) => { | 279 | {runtimeModeOptions.map((mode) => { |
| 281 | const option = runtimeModeConfig[mode]; | 280 | const option = runtimeModeConfig[mode]; |
| 282 | const OptionIcon = option.icon; | 281 | const OptionIcon = option.icon; |
| 283 | const isSelected = props.runtimeMode === mode; | ||
| 284 | return ( | 282 | return ( |
| 285 | <SelectItem key={mode} value={mode} hideIndicator className="min-w-64 py-2"> | 283 | <SelectItem key={mode} value={mode} hideIndicator className="min-w-64 py-2"> |
| 286 | <div className="flex min-w-0 items-center gap-3"> | 284 | <div className="flex min-w-0 items-center gap-3"> |
| ... | @@ -293,12 +291,6 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop | ... | @@ -293,12 +291,6 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop |
| 293 | {option.description} | 291 | {option.description} |
| 294 | </span> | 292 | </span> |
| 295 | </div> | 293 | </div> |
| 296 | <CheckIcon | ||
| 297 | className={cn( | ||
| 298 | "size-4 text-blue-400", | ||
| 299 | isSelected ? "opacity-100" : "opacity-0", | ||
| 300 | )} | ||
| 301 | /> | ||
| 302 | </div> | 294 | </div> |
| 303 | </SelectItem> | 295 | </SelectItem> |
| 304 | ); | 296 | ); |
| ... | @@ -2162,10 +2154,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -2162,10 +2154,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 2162 | ref={composerSurfaceRef} | 2154 | ref={composerSurfaceRef} |
| 2163 | data-chat-composer-mobile-collapsed={isComposerCollapsedMobile ? "true" : "false"} | 2155 | data-chat-composer-mobile-collapsed={isComposerCollapsedMobile ? "true" : "false"} |
| 2164 | className={cn( | 2156 | className={cn( |
| 2165 | "chat-composer-glass rounded-[20px] border transition-[background-color] duration-200 has-focus-visible:border-foreground/40", | 2157 | "chat-composer-glass rounded-[20px] transition-[background-color] duration-200", |
| 2166 | isDragOverComposer | 2158 | isDragOverComposer ? "bg-accent/45 ring-1 ring-primary/70" : null, |
| 2167 | ? "border-primary/70 bg-accent/45" | ||
| 2168 | : "border-black/12 dark:border-transparent dark:inset-ring-1 dark:inset-ring-white/5", | ||
| 2169 | projectSelectionRequired ? "opacity-75" : null, | 2159 | projectSelectionRequired ? "opacity-75" : null, |
| 2170 | composerProviderState.composerSurfaceClassName, | 2160 | composerProviderState.composerSurfaceClassName, |
| 2171 | )} | 2161 | )} |
apps/web/src/components/chat/ComposerPrimaryActions.tsx+13-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import { memo, type PointerEventHandler } from "react"; | 1 | import { memo, type PointerEventHandler } from "react"; |
| 2 | import { ChevronDownIcon, ChevronLeftIcon } from "lucide-react"; | 2 | import { ChevronDownIcon, ChevronLeftIcon } from "lucide-react"; |
| 3 | import { cn } from "~/lib/utils"; | 3 | import { cn } from "~/lib/utils"; |
| 4 | import { StageBackdropButtonArt, useSidebarStageBackdropVariant } from "../SidebarStageBackdrop"; | ||
| 4 | import { Button } from "../ui/button"; | 5 | import { Button } from "../ui/button"; |
| 5 | import { Menu, MenuItem, MenuPopup, MenuTrigger } from "../ui/menu"; | 6 | import { Menu, MenuItem, MenuPopup, MenuTrigger } from "../ui/menu"; |
| 6 | import { Spinner } from "../ui/spinner"; | 7 | import { Spinner } from "../ui/spinner"; |
| ... | @@ -71,6 +72,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ | ... | @@ -71,6 +72,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 71 | const pointerFocusProps = preserveComposerFocusOnPointerDown | 72 | const pointerFocusProps = preserveComposerFocusOnPointerDown |
| 72 | ? { onPointerDown: preventPointerFocus } | 73 | ? { onPointerDown: preventPointerFocus } |
| 73 | : undefined; | 74 | : undefined; |
| 75 | const stageBackdropVariant = useSidebarStageBackdropVariant(); | ||
| 74 | 76 | ||
| 75 | if (pendingAction) { | 77 | if (pendingAction) { |
| 76 | return ( | 78 | return ( |
| ... | @@ -196,7 +198,12 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ | ... | @@ -196,7 +198,12 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 196 | return ( | 198 | return ( |
| 197 | <button | 199 | <button |
| 198 | type="submit" | 200 | type="submit" |
| 199 | className="flex h-9 w-9 enabled:cursor-pointer items-center justify-center rounded-full bg-primary/90 text-primary-foreground shadow-xs enabled:shadow-primary/24 enabled:inset-shadow-[0_1px_--theme(--color-white/16%)] transition-all duration-150 hover:bg-primary hover:scale-105 active:inset-shadow-[0_1px_--theme(--color-black/8%)] active:shadow-none disabled:pointer-events-none disabled:opacity-30 disabled:shadow-none disabled:hover:scale-100 sm:h-8 sm:w-8" | 201 | className={cn( |
| 202 | "relative isolate flex h-9 w-9 items-center justify-center overflow-hidden rounded-full text-primary-foreground shadow-xs transition-all duration-150 enabled:cursor-pointer enabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:scale-105 active:inset-shadow-[0_1px_--theme(--color-black/8%)] active:shadow-none disabled:pointer-events-none disabled:opacity-30 disabled:shadow-none disabled:hover:scale-100 sm:h-8 sm:w-8", | ||
| 203 | stageBackdropVariant | ||
| 204 | ? "bg-transparent enabled:shadow-black/24 enabled:hover:brightness-110" | ||
| 205 | : "bg-primary/90 enabled:shadow-primary/24 hover:bg-primary", | ||
| 206 | )} | ||
| 200 | {...pointerFocusProps} | 207 | {...pointerFocusProps} |
| 201 | disabled={isSendBusy || isConnecting || isEnvironmentUnavailable || !hasSendableContent} | 208 | disabled={isSendBusy || isConnecting || isEnvironmentUnavailable || !hasSendableContent} |
| 202 | aria-label={ | 209 | aria-label={ |
| ... | @@ -211,6 +218,11 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ | ... | @@ -211,6 +218,11 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 211 | : "Send message" | 218 | : "Send message" |
| 212 | } | 219 | } |
| 213 | > | 220 | > |
| 221 | {stageBackdropVariant ? ( | ||
| 222 | <span className="absolute inset-0 -z-10" aria-hidden="true"> | ||
| 223 | <StageBackdropButtonArt variant={stageBackdropVariant} /> | ||
| 224 | </span> | ||
| 225 | ) : null} | ||
| 214 | {isConnecting || isSendBusy ? ( | 226 | {isConnecting || isSendBusy ? ( |
| 215 | <Spinner className="size-3.5" aria-hidden="true" /> | 227 | <Spinner className="size-3.5" aria-hidden="true" /> |
| 216 | ) : ( | 228 | ) : ( |
apps/web/src/components/chat/ContextWindowMeter.tsx+12-5| ... | @@ -25,7 +25,9 @@ export function ContextWindowMeter(props: { | ... | @@ -25,7 +25,9 @@ export function ContextWindowMeter(props: { |
| 25 | const totalProcessedTokens = usage.totalProcessedTokens ?? null; | 25 | const totalProcessedTokens = usage.totalProcessedTokens ?? null; |
| 26 | const showTotalProcessed = totalProcessedTokens !== null && totalProcessedTokens > 0; | 26 | const showTotalProcessed = totalProcessedTokens !== null && totalProcessedTokens > 0; |
| 27 | const isOverloaded = normalizedPercentage > 90; | 27 | const isOverloaded = normalizedPercentage > 90; |
| 28 | const usageColor = isOverloaded ? "var(--color-red-500)" : "var(--color-blue-500)"; | 28 | const usageColor = isOverloaded |
| 29 | ? "var(--color-red-500)" | ||
| 30 | : "color-mix(in oklab, var(--color-muted-foreground) 72%, transparent)"; | ||
| 29 | 31 | ||
| 30 | return ( | 32 | return ( |
| 31 | <Popover> | 33 | <Popover> |
| ... | @@ -37,7 +39,7 @@ export function ContextWindowMeter(props: { | ... | @@ -37,7 +39,7 @@ export function ContextWindowMeter(props: { |
| 37 | <button | 39 | <button |
| 38 | type="button" | 40 | type="button" |
| 39 | className={cn( | 41 | className={cn( |
| 40 | "inline-flex size-6 cursor-pointer items-center justify-center rounded-full border border-transparent text-muted-foreground outline-none transition-colors", | 42 | "inline-flex size-7 cursor-pointer items-center justify-center rounded-full border border-transparent text-muted-foreground outline-none transition-colors", |
| 41 | "hover:bg-accent data-[pressed]:bg-accent", | 43 | "hover:bg-accent data-[pressed]:bg-accent", |
| 42 | "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", | 44 | "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", |
| 43 | )} | 45 | )} |
| ... | @@ -47,7 +49,7 @@ export function ContextWindowMeter(props: { | ... | @@ -47,7 +49,7 @@ export function ContextWindowMeter(props: { |
| 47 | : `Context window ${formatContextWindowTokens(usage.usedTokens)} tokens used` | 49 | : `Context window ${formatContextWindowTokens(usage.usedTokens)} tokens used` |
| 48 | } | 50 | } |
| 49 | > | 51 | > |
| 50 | <span className="relative flex size-4 items-center justify-center"> | 52 | <span className="relative flex size-5 items-center justify-center"> |
| 51 | <svg | 53 | <svg |
| 52 | viewBox="0 0 24 24" | 54 | viewBox="0 0 24 24" |
| 53 | className="-rotate-90 absolute inset-0 size-full transform-gpu" | 55 | className="-rotate-90 absolute inset-0 size-full transform-gpu" |
| ... | @@ -58,7 +60,7 @@ export function ContextWindowMeter(props: { | ... | @@ -58,7 +60,7 @@ export function ContextWindowMeter(props: { |
| 58 | cy="12" | 60 | cy="12" |
| 59 | r={radius} | 61 | r={radius} |
| 60 | fill="none" | 62 | fill="none" |
| 61 | stroke="color-mix(in oklab, var(--color-muted-foreground) 35%, transparent)" | 63 | stroke="color-mix(in oklab, var(--color-muted-foreground) 24%, transparent)" |
| 62 | strokeWidth="3" | 64 | strokeWidth="3" |
| 63 | /> | 65 | /> |
| 64 | <circle | 66 | <circle |
| ... | @@ -78,7 +80,12 @@ export function ContextWindowMeter(props: { | ... | @@ -78,7 +80,12 @@ export function ContextWindowMeter(props: { |
| 78 | </button> | 80 | </button> |
| 79 | } | 81 | } |
| 80 | /> | 82 | /> |
| 81 | <PopoverPopup tooltipStyle side="top" align="end" className="w-64 max-w-none p-0"> | 83 | <PopoverPopup |
| 84 | tooltipStyle | ||
| 85 | side="top" | ||
| 86 | align="end" | ||
| 87 | className="dropdown-glass w-64 max-w-none border-0! bg-secondary! p-0 shadow-none! before:hidden" | ||
| 88 | > | ||
| 82 | <div className="flex flex-col gap-2 p-3"> | 89 | <div className="flex flex-col gap-2 p-3"> |
| 83 | <div className="flex items-center justify-between gap-3"> | 90 | <div className="flex items-center justify-between gap-3"> |
| 84 | <div className="font-medium text-muted-foreground text-xs">Context Window</div> | 91 | <div className="font-medium text-muted-foreground text-xs">Context Window</div> |
apps/web/src/components/chat/MessagesTimeline.test.tsx+17| ... | @@ -219,6 +219,23 @@ function buildUserTimelineEntry(text: string) { | ... | @@ -219,6 +219,23 @@ function buildUserTimelineEntry(text: string) { |
| 219 | } | 219 | } |
| 220 | 220 | ||
| 221 | describe("MessagesTimeline", () => { | 221 | describe("MessagesTimeline", () => { |
| 222 | it("uses the larger leading inset only when the top fade is enabled", async () => { | ||
| 223 | const { MessagesTimeline } = await import("./MessagesTimeline"); | ||
| 224 | const timelineEntries = [buildUserTimelineEntry("Hello")]; | ||
| 225 | |||
| 226 | const compactMarkup = renderToStaticMarkup( | ||
| 227 | <MessagesTimeline {...buildProps()} timelineEntries={timelineEntries} />, | ||
| 228 | ); | ||
| 229 | const fadedMarkup = renderToStaticMarkup( | ||
| 230 | <MessagesTimeline {...buildProps()} timelineEntries={timelineEntries} topFadeEnabled />, | ||
| 231 | ); | ||
| 232 | |||
| 233 | expect(compactMarkup).toContain('class="h-3 sm:h-4"'); | ||
| 234 | expect(compactMarkup).not.toContain("chat-timeline-scroll-fade"); | ||
| 235 | expect(fadedMarkup).toContain('class="h-10 sm:h-12"'); | ||
| 236 | expect(fadedMarkup).toContain("chat-timeline-scroll-fade"); | ||
| 237 | }); | ||
| 238 | |||
| 222 | it("keeps assistant changed-files headers sticky below the thread header", async () => { | 239 | it("keeps assistant changed-files headers sticky below the thread header", async () => { |
| 223 | const { MessagesTimeline } = await import("./MessagesTimeline"); | 240 | const { MessagesTimeline } = await import("./MessagesTimeline"); |
| 224 | const assistantMessageId = MessageId.make("message-assistant-with-files"); | 241 | const assistantMessageId = MessageId.make("message-assistant-with-files"); |
apps/web/src/components/chat/MessagesTimeline.tsx+11-5| ... | @@ -150,6 +150,7 @@ interface TimelineRowActivityState { | ... | @@ -150,6 +150,7 @@ interface TimelineRowActivityState { |
| 150 | const TimelineRowCtx = createContext<TimelineRowSharedState>(null!); | 150 | const TimelineRowCtx = createContext<TimelineRowSharedState>(null!); |
| 151 | const TimelineRowActivityCtx = createContext<TimelineRowActivityState>(null!); | 151 | const TimelineRowActivityCtx = createContext<TimelineRowActivityState>(null!); |
| 152 | const TIMELINE_LIST_HEADER = <div className="h-3 sm:h-4" />; | 152 | const TIMELINE_LIST_HEADER = <div className="h-3 sm:h-4" />; |
| 153 | const TIMELINE_LIST_FADE_HEADER = <div className="h-10 sm:h-12" />; | ||
| 153 | const TIMELINE_LIST_FOOTER = <div className="h-3 sm:h-4" />; | 154 | const TIMELINE_LIST_FOOTER = <div className="h-3 sm:h-4" />; |
| 154 | const EMPTY_TIMELINE_SKILLS: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">> = []; | 155 | const EMPTY_TIMELINE_SKILLS: ReadonlyArray<Pick<ServerProviderSkill, "name" | "displayName">> = []; |
| 155 | 156 | ||
| ... | @@ -185,6 +186,7 @@ interface MessagesTimelineProps { | ... | @@ -185,6 +186,7 @@ interface MessagesTimelineProps { |
| 185 | onIsAtEndChange: (isAtEnd: boolean) => void; | 186 | onIsAtEndChange: (isAtEnd: boolean) => void; |
| 186 | onManualNavigation: () => void; | 187 | onManualNavigation: () => void; |
| 187 | hideEmptyPlaceholder?: boolean; | 188 | hideEmptyPlaceholder?: boolean; |
| 189 | topFadeEnabled?: boolean; | ||
| 188 | } | 190 | } |
| 189 | 191 | ||
| 190 | // --------------------------------------------------------------------------- | 192 | // --------------------------------------------------------------------------- |
| ... | @@ -219,6 +221,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ | ... | @@ -219,6 +221,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ |
| 219 | onIsAtEndChange, | 221 | onIsAtEndChange, |
| 220 | onManualNavigation, | 222 | onManualNavigation, |
| 221 | hideEmptyPlaceholder = false, | 223 | hideEmptyPlaceholder = false, |
| 224 | topFadeEnabled = false, | ||
| 222 | }: MessagesTimelineProps) { | 225 | }: MessagesTimelineProps) { |
| 223 | const [expandedTurnIds, setExpandedTurnIds] = useState<ReadonlySet<TurnId>>(new Set()); | 226 | const [expandedTurnIds, setExpandedTurnIds] = useState<ReadonlySet<TurnId>>(new Set()); |
| 224 | const [expandedWorkGroupIds, setExpandedWorkGroupIds] = useState<ReadonlySet<string>>(new Set()); | 227 | const [expandedWorkGroupIds, setExpandedWorkGroupIds] = useState<ReadonlySet<string>>(new Set()); |
| ... | @@ -510,8 +513,11 @@ export const MessagesTimeline = memo(function MessagesTimeline({ | ... | @@ -510,8 +513,11 @@ export const MessagesTimeline = memo(function MessagesTimeline({ |
| 510 | size: false, | 513 | size: false, |
| 511 | }} | 514 | }} |
| 512 | onScroll={handleScroll} | 515 | onScroll={handleScroll} |
| 513 | className="scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5" | 516 | className={cn( |
| 514 | ListHeaderComponent={TIMELINE_LIST_HEADER} | 517 | "scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5", |
| 518 | topFadeEnabled && "chat-timeline-scroll-fade", | ||
| 519 | )} | ||
| 520 | ListHeaderComponent={topFadeEnabled ? TIMELINE_LIST_FADE_HEADER : TIMELINE_LIST_HEADER} | ||
| 515 | ListFooterComponent={TIMELINE_LIST_FOOTER} | 521 | ListFooterComponent={TIMELINE_LIST_FOOTER} |
| 516 | /> | 522 | /> |
| 517 | <TimelineMinimap | 523 | <TimelineMinimap |
| ... | @@ -797,7 +803,7 @@ function TimelineMinimap({ | ... | @@ -797,7 +803,7 @@ function TimelineMinimap({ |
| 797 | transform: `translateY(${activeTooltipTranslate})`, | 803 | transform: `translateY(${activeTooltipTranslate})`, |
| 798 | }} | 804 | }} |
| 799 | > | 805 | > |
| 800 | <span className="block rounded-xl border border-border/70 bg-popover/95 p-3 text-left text-popover-foreground shadow-xl shadow-black/25 backdrop-blur"> | 806 | <span className="dropdown-glass block rounded-xl p-3 text-left text-popover-foreground shadow-xl shadow-black/25"> |
| 801 | <span className="block max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium leading-5"> | 807 | <span className="block max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium leading-5"> |
| 802 | {activeItem.userText ?? "User message"} | 808 | {activeItem.userText ?? "User message"} |
| 803 | </span> | 809 | </span> |
| ... | @@ -886,7 +892,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message" | ... | @@ -886,7 +892,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message" |
| 886 | 892 | ||
| 887 | return ( | 893 | return ( |
| 888 | <div className="group flex flex-col items-end gap-1"> | 894 | <div className="group flex flex-col items-end gap-1"> |
| 889 | <div className="relative max-w-[80%] rounded-2xl border border-border bg-secondary p-3"> | 895 | <div className="relative max-w-[80%] rounded-2xl bg-secondary p-3"> |
| 890 | {regularImages.length > 0 && ( | 896 | {regularImages.length > 0 && ( |
| 891 | <div className="mb-2 grid max-w-[420px] grid-cols-2 gap-2"> | 897 | <div className="mb-2 grid max-w-[420px] grid-cols-2 gap-2"> |
| 892 | {regularImages.map((image: NonNullable<TimelineMessage["attachments"]>[number]) => ( | 898 | {regularImages.map((image: NonNullable<TimelineMessage["attachments"]>[number]) => ( |
| ... | @@ -1279,7 +1285,7 @@ function AssistantChangedFilesSectionInner({ | ... | @@ -1279,7 +1285,7 @@ function AssistantChangedFilesSectionInner({ |
| 1279 | const summaryStat = summarizeTurnDiffStats(checkpointFiles); | 1285 | const summaryStat = summarizeTurnDiffStats(checkpointFiles); |
| 1280 | 1286 | ||
| 1281 | return ( | 1287 | return ( |
| 1282 | <div className="mt-4 rounded-2xl border border-input bg-background p-2 pt-4 shadow-xs/5 not-dark:bg-clip-padding dark:bg-input/32"> | 1288 | <div className="mt-4 rounded-2xl bg-background p-2 pt-4 shadow-xs/5 not-dark:bg-clip-padding dark:bg-input/32"> |
| 1283 | <div className="sticky top-2 z-10 mb-3 flex items-center justify-between gap-2 bg-background px-2 before:absolute before:inset-x-0 before:-top-4 before:h-4 before:bg-background before:content-[''] dark:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))] dark:before:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))]"> | 1289 | <div className="sticky top-2 z-10 mb-3 flex items-center justify-between gap-2 bg-background px-2 before:absolute before:inset-x-0 before:-top-4 before:h-4 before:bg-background before:content-[''] dark:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))] dark:before:bg-[color-mix(in_srgb,var(--foreground)_2.5%,var(--background))]"> |
| 1284 | <p className="flex items-center gap-1 whitespace-nowrap font-medium text-foreground text-xs leading-4"> | 1290 | <p className="flex items-center gap-1 whitespace-nowrap font-medium text-foreground text-xs leading-4"> |
| 1285 | <span> | 1291 | <span> |
apps/web/src/components/chat/ModelListRow.tsx+2-3| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import { type ProviderDriverKind, type ProviderInstanceId } from "@t3tools/contracts"; | 1 | import { type ProviderDriverKind, type ProviderInstanceId } from "@t3tools/contracts"; |
| 2 | import { memo } from "react"; | 2 | import { memo } from "react"; |
| 3 | import { CheckIcon, StarIcon } from "lucide-react"; | 3 | import { StarIcon } from "lucide-react"; |
| 4 | import { | 4 | import { |
| 5 | getDisplayModelName, | 5 | getDisplayModelName, |
| 6 | getTriggerDisplayModelLabel, | 6 | getTriggerDisplayModelLabel, |
| ... | @@ -51,7 +51,7 @@ export const ModelListRow = memo(function ModelListRow(props: { | ... | @@ -51,7 +51,7 @@ export const ModelListRow = memo(function ModelListRow(props: { |
| 51 | contentClassName="flex w-full items-center gap-3" | 51 | contentClassName="flex w-full items-center gap-3" |
| 52 | className={cn( | 52 | className={cn( |
| 53 | "group relative w-full !min-w-0 max-w-full cursor-pointer rounded-md px-2 py-2.5 transition-[background-color,box-shadow,color]", | 53 | "group relative w-full !min-w-0 max-w-full cursor-pointer rounded-md px-2 py-2.5 transition-[background-color,box-shadow,color]", |
| 54 | "data-highlighted:bg-muted/56 data-selected:bg-transparent data-selected:text-foreground data-selected:ring-0", | 54 | "data-highlighted:bg-muted/56 data-selected:bg-foreground/[0.08] data-selected:text-foreground data-selected:ring-0", |
| 55 | props.disabledReason && | 55 | props.disabledReason && |
| 56 | "data-disabled:pointer-events-auto data-disabled:cursor-not-allowed data-disabled:hover:bg-transparent", | 56 | "data-disabled:pointer-events-auto data-disabled:cursor-not-allowed data-disabled:hover:bg-transparent", |
| 57 | )} | 57 | )} |
| ... | @@ -66,7 +66,6 @@ export const ModelListRow = memo(function ModelListRow(props: { | ... | @@ -66,7 +66,6 @@ export const ModelListRow = memo(function ModelListRow(props: { |
| 66 | props.preferShortName ? { preferShortName: true } : undefined, | 66 | props.preferShortName ? { preferShortName: true } : undefined, |
| 67 | )} | 67 | )} |
| 68 | </div> | 68 | </div> |
| 69 | {props.isSelected ? <CheckIcon className="size-3.5 shrink-0 text-blue-400" /> : null} | ||
| 70 | {props.showNewBadge ? ( | 69 | {props.showNewBadge ? ( |
| 71 | <span | 70 | <span |
| 72 | className="shrink-0 rounded border border-amber-500/35 bg-amber-500/15 px-0.5 py-px text-[10px] font-bold uppercase leading-none tracking-wide text-amber-800 dark:border-amber-400/30 dark:bg-amber-400/12 dark:text-amber-200" | 71 | className="shrink-0 rounded border border-amber-500/35 bg-amber-500/15 px-0.5 py-px text-[10px] font-bold uppercase leading-none tracking-wide text-amber-800 dark:border-amber-400/30 dark:bg-amber-400/12 dark:text-amber-200" |
apps/web/src/components/chat/ModelPickerContent.tsx+3-8| ... | @@ -521,7 +521,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -521,7 +521,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 521 | return ( | 521 | return ( |
| 522 | <TooltipProvider delay={0}> | 522 | <TooltipProvider delay={0}> |
| 523 | <div | 523 | <div |
| 524 | className="relative flex h-screen max-h-96 w-screen max-w-100 flex-row overflow-hidden rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:border-transparent dark:shadow-none dark:inset-ring-1 dark:inset-ring-white/5 dark:before:shadow-none" | 524 | className="dropdown-glass model-picker-surface relative flex h-screen max-h-96 w-screen max-w-100 flex-row overflow-hidden rounded-xl text-popover-foreground" |
| 525 | data-model-picker-content="true" | 525 | data-model-picker-content="true" |
| 526 | > | 526 | > |
| 527 | {/* Sidebar */} | 527 | {/* Sidebar */} |
| ... | @@ -568,15 +568,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -568,15 +568,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 568 | handleModelSelect(slug, instanceId); | 568 | handleModelSelect(slug, instanceId); |
| 569 | }} | 569 | }} |
| 570 | > | 570 | > |
| 571 | <div | 571 | <div className="flex min-h-0 flex-1 flex-col overflow-hidden"> |
| 572 | className={cn( | ||
| 573 | "flex min-h-0 flex-1 flex-col overflow-hidden bg-muted/40", | ||
| 574 | showSidebar && "border-l", | ||
| 575 | )} | ||
| 576 | > | ||
| 577 | {/* Search bar */} | 572 | {/* Search bar */} |
| 578 | <div className="px-4 pt-2.5"> | 573 | <div className="px-4 pt-2.5"> |
| 579 | <div className="-translate-y-px border-b border-border/70 pb-2.5 transition-colors focus-within:border-ring"> | 574 | <div className="-translate-y-px rounded-lg bg-accent px-2 py-1"> |
| 580 | <ComboboxInput | 575 | <ComboboxInput |
| 581 | ref={searchInputRef} | 576 | ref={searchInputRef} |
| 582 | className="[&_input]:h-6.5 [&_input]:font-sans [&_input]:leading-6.5" | 577 | className="[&_input]:h-6.5 [&_input]:font-sans [&_input]:leading-6.5" |
apps/web/src/components/chat/ModelPickerSidebar.tsx+2-5| ... | @@ -97,10 +97,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { | ... | @@ -97,10 +97,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { |
| 97 | }, [props.instanceEntries, props.selectedInstanceId, showFavorites]); | 97 | }, [props.instanceEntries, props.selectedInstanceId, showFavorites]); |
| 98 | 98 | ||
| 99 | return ( | 99 | return ( |
| 100 | <div | 100 | <div className="w-12 shrink-0 overflow-hidden bg-muted/60" data-model-picker-sidebar="true"> |
| 101 | className="w-12 shrink-0 overflow-hidden border-r bg-muted/30" | ||
| 102 | data-model-picker-sidebar="true" | ||
| 103 | > | ||
| 104 | <div className="h-full overflow-y-auto overscroll-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> | 101 | <div className="h-full overflow-y-auto overscroll-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> |
| 105 | <div | 102 | <div |
| 106 | ref={sidebarContentRef} | 103 | ref={sidebarContentRef} |
| ... | @@ -118,7 +115,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { | ... | @@ -118,7 +115,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { |
| 118 | ) : null} | 115 | ) : null} |
| 119 | {/* Favorites section */} | 116 | {/* Favorites section */} |
| 120 | {showFavorites ? ( | 117 | {showFavorites ? ( |
| 121 | <div className="mb-1 border-b pb-1"> | 118 | <div className="mb-1 pb-1"> |
| 122 | <div className="relative w-full"> | 119 | <div className="relative w-full"> |
| 123 | <Tooltip> | 120 | <Tooltip> |
| 124 | <TooltipTrigger | 121 | <TooltipTrigger |
apps/web/src/components/chat/ProviderStatusBanner.tsx+1-1| ... | @@ -27,7 +27,7 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ | ... | @@ -27,7 +27,7 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ |
| 27 | : `${providerName} provider has limited availability.`)); | 27 | : `${providerName} provider has limited availability.`)); |
| 28 | 28 | ||
| 29 | return ( | 29 | return ( |
| 30 | <div className="mx-auto w-fit max-w-[calc(100%-2rem)] pt-3"> | 30 | <div className="pointer-events-auto mx-auto w-fit max-w-[calc(100%-2rem)] pt-3"> |
| 31 | <div | 31 | <div |
| 32 | className={cn( | 32 | className={cn( |
| 33 | "inline-flex items-center gap-3 rounded-xl border px-3.5 py-3 text-card-foreground text-sm", | 33 | "inline-flex items-center gap-3 rounded-xl border px-3.5 py-3 text-card-foreground text-sm", |
apps/web/src/components/chat/TraitsPicker.tsx+1-7| ... | @@ -16,7 +16,7 @@ import { | ... | @@ -16,7 +16,7 @@ import { |
| 16 | } from "@t3tools/shared/model"; | 16 | } from "@t3tools/shared/model"; |
| 17 | import { memo, useCallback, useState } from "react"; | 17 | import { memo, useCallback, useState } from "react"; |
| 18 | import type { VariantProps } from "class-variance-authority"; | 18 | import type { VariantProps } from "class-variance-authority"; |
| 19 | import { CheckIcon, ChevronDownIcon } from "lucide-react"; | 19 | import { ChevronDownIcon } from "lucide-react"; |
| 20 | import { Button, buttonVariants } from "../ui/button"; | 20 | import { Button, buttonVariants } from "../ui/button"; |
| 21 | import { | 21 | import { |
| 22 | Menu, | 22 | Menu, |
| ... | @@ -337,9 +337,6 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ | ... | @@ -337,9 +337,6 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ |
| 337 | </> | 337 | </> |
| 338 | ) : null} | 338 | ) : null} |
| 339 | </span> | 339 | </span> |
| 340 | {option.id === selectedValue ? ( | ||
| 341 | <CheckIcon className="size-3.5 shrink-0 text-blue-400" /> | ||
| 342 | ) : null} | ||
| 343 | </span> | 340 | </span> |
| 344 | </MenuRadioItem> | 341 | </MenuRadioItem> |
| 345 | ))} | 342 | ))} |
| ... | @@ -370,9 +367,6 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ | ... | @@ -370,9 +367,6 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ |
| 370 | <MenuRadioItem key={value} value={value} hideIndicator> | 367 | <MenuRadioItem key={value} value={value} hideIndicator> |
| 371 | <span className="flex w-full min-w-0 items-center justify-between gap-3"> | 368 | <span className="flex w-full min-w-0 items-center justify-between gap-3"> |
| 372 | <span>{value === "on" ? "On" : "Off"}</span> | 369 | <span>{value === "on" ? "On" : "Off"}</span> |
| 373 | {value === selectedValue ? ( | ||
| 374 | <CheckIcon className="size-3.5 shrink-0 text-blue-400" /> | ||
| 375 | ) : null} | ||
| 376 | </span> | 370 | </span> |
| 377 | </MenuRadioItem> | 371 | </MenuRadioItem> |
| 378 | ))} | 372 | ))} |
apps/web/src/components/settings/ConnectionsSettings.tsx+3-6| ... | @@ -373,7 +373,7 @@ function formatDesktopSshConnectionError(error: unknown): string { | ... | @@ -373,7 +373,7 @@ function formatDesktopSshConnectionError(error: unknown): string { |
| 373 | return withoutTaggedErrorPrefix.trim() || fallback; | 373 | return withoutTaggedErrorPrefix.trim() || fallback; |
| 374 | } | 374 | } |
| 375 | 375 | ||
| 376 | const ENDPOINT_ROW_CLASSNAME = "border-t border-border/60 px-4 py-2.5 first:border-t-0 sm:px-5"; | 376 | const ENDPOINT_ROW_CLASSNAME = "rounded-xl px-3 py-2.5 sm:px-4"; |
| 377 | 377 | ||
| 378 | type AccessSectionPresentation = "current" | "endpoint-rail"; | 378 | type AccessSectionPresentation = "current" | "endpoint-rail"; |
| 379 | 379 | ||
| ... | @@ -383,10 +383,7 @@ function accessRowClassName(_presentation: AccessSectionPresentation) { | ... | @@ -383,10 +383,7 @@ function accessRowClassName(_presentation: AccessSectionPresentation) { |
| 383 | 383 | ||
| 384 | function endpointRowClassName(presentation: AccessSectionPresentation, isAvailable: boolean) { | 384 | function endpointRowClassName(presentation: AccessSectionPresentation, isAvailable: boolean) { |
| 385 | if (presentation === "endpoint-rail") { | 385 | if (presentation === "endpoint-rail") { |
| 386 | return cn( | 386 | return cn("relative rounded-xl px-3 py-3 sm:px-4", !isAvailable && "bg-muted/15"); |
| 387 | "relative border-t border-border/60 px-4 py-3 first:border-t-0 sm:px-5", | ||
| 388 | !isAvailable && "bg-muted/20", | ||
| 389 | ); | ||
| 390 | } | 387 | } |
| 391 | 388 | ||
| 392 | return cn(ENDPOINT_ROW_CLASSNAME, !isAvailable && "bg-muted/24"); | 389 | return cn(ENDPOINT_ROW_CLASSNAME, !isAvailable && "bg-muted/24"); |
| ... | @@ -1524,7 +1521,7 @@ const DesktopSshHostRow = memo(function DesktopSshHostRow({ | ... | @@ -1524,7 +1521,7 @@ const DesktopSshHostRow = memo(function DesktopSshHostRow({ |
| 1524 | const buttonLabel = connectingHostAlias === target.alias ? "Adding…" : "Add environment"; | 1521 | const buttonLabel = connectingHostAlias === target.alias ? "Adding…" : "Add environment"; |
| 1525 | 1522 | ||
| 1526 | return ( | 1523 | return ( |
| 1527 | <div className="border-t border-border/60 px-4 py-3 first:border-t-0 sm:px-5"> | 1524 | <div className="rounded-xl px-3 py-3 sm:px-4"> |
| 1528 | <div className={ITEM_ROW_INNER_CLASSNAME}> | 1525 | <div className={ITEM_ROW_INNER_CLASSNAME}> |
| 1529 | <div className="min-w-0 flex-1"> | 1526 | <div className="min-w-0 flex-1"> |
| 1530 | <h3 className="truncate text-sm font-medium text-foreground">{target.alias}</h3> | 1527 | <h3 className="truncate text-sm font-medium text-foreground">{target.alias}</h3> |
apps/web/src/components/settings/ProviderInstanceCard.tsx+9-9| ... | @@ -531,7 +531,7 @@ export function ProviderInstanceCard({ | ... | @@ -531,7 +531,7 @@ export function ProviderInstanceCard({ |
| 531 | const titleHeadNode = ( | 531 | const titleHeadNode = ( |
| 532 | <> | 532 | <> |
| 533 | {titleIconNode} | 533 | {titleIconNode} |
| 534 | <h3 className="truncate text-[13px] font-semibold tracking-[-0.01em] text-foreground"> | 534 | <h3 className="truncate text-sm font-medium tracking-[-0.005em] text-foreground"> |
| 535 | {displayName} | 535 | {displayName} |
| 536 | </h3> | 536 | </h3> |
| 537 | {String(instanceId) !== String(instance.driver) ? ( | 537 | {String(instanceId) !== String(instance.driver) ? ( |
| ... | @@ -578,7 +578,7 @@ export function ProviderInstanceCard({ | ... | @@ -578,7 +578,7 @@ export function ProviderInstanceCard({ |
| 578 | ); | 578 | ); |
| 579 | 579 | ||
| 580 | const authRowNode = ( | 580 | const authRowNode = ( |
| 581 | <p className="flex min-w-0 flex-wrap items-center gap-x-1 text-xs text-muted-foreground/80"> | 581 | <p className="flex min-w-0 flex-wrap items-center gap-x-1 text-[13px] leading-[1.45] text-muted-foreground/80"> |
| 582 | {hasAuthenticatedEmail ? ( | 582 | {hasAuthenticatedEmail ? ( |
| 583 | <> | 583 | <> |
| 584 | <span>Authenticated as</span> | 584 | <span>Authenticated as</span> |
| ... | @@ -600,8 +600,8 @@ export function ProviderInstanceCard({ | ... | @@ -600,8 +600,8 @@ export function ProviderInstanceCard({ |
| 600 | ) : null; | 600 | ) : null; |
| 601 | 601 | ||
| 602 | return ( | 602 | return ( |
| 603 | <div className="border-t border-border/60 first:border-t-0"> | 603 | <div className="rounded-xl transition-colors hover:bg-muted/20"> |
| 604 | <div className="px-4 py-3.5 sm:px-5"> | 604 | <div className="px-3 py-3 sm:px-4"> |
| 605 | <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> | 605 | <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> |
| 606 | <div className="min-w-0 flex-1 space-y-1"> | 606 | <div className="min-w-0 flex-1 space-y-1"> |
| 607 | <div className="flex min-w-0 flex-wrap items-center gap-2"> | 607 | <div className="flex min-w-0 flex-wrap items-center gap-2"> |
| ... | @@ -729,8 +729,8 @@ export function ProviderInstanceCard({ | ... | @@ -729,8 +729,8 @@ export function ProviderInstanceCard({ |
| 729 | 729 | ||
| 730 | <Collapsible open={isExpanded} onOpenChange={onExpandedChange}> | 730 | <Collapsible open={isExpanded} onOpenChange={onExpandedChange}> |
| 731 | <CollapsibleContent> | 731 | <CollapsibleContent> |
| 732 | <div className="space-y-0"> | 732 | <div className="space-y-5 px-3 pb-4 pt-2 sm:px-4"> |
| 733 | <div className="border-t border-border/60 px-4 py-3 sm:px-5"> | 733 | <div> |
| 734 | <label htmlFor={`provider-instance-${instanceId}-display-name`} className="block"> | 734 | <label htmlFor={`provider-instance-${instanceId}-display-name`} className="block"> |
| 735 | <span className="text-xs font-medium text-foreground">Display name</span> | 735 | <span className="text-xs font-medium text-foreground">Display name</span> |
| 736 | <DraftInput | 736 | <DraftInput |
| ... | @@ -747,7 +747,7 @@ export function ProviderInstanceCard({ | ... | @@ -747,7 +747,7 @@ export function ProviderInstanceCard({ |
| 747 | </label> | 747 | </label> |
| 748 | </div> | 748 | </div> |
| 749 | 749 | ||
| 750 | <div className="border-t border-border/60 px-4 py-3 sm:px-5"> | 750 | <div> |
| 751 | <ProviderAccentColorPicker | 751 | <ProviderAccentColorPicker |
| 752 | displayName={displayName} | 752 | displayName={displayName} |
| 753 | value={accentColor} | 753 | value={accentColor} |
| ... | @@ -757,7 +757,7 @@ export function ProviderInstanceCard({ | ... | @@ -757,7 +757,7 @@ export function ProviderInstanceCard({ |
| 757 | /> | 757 | /> |
| 758 | </div> | 758 | </div> |
| 759 | 759 | ||
| 760 | <div className="border-t border-border/60 px-4 py-3 sm:px-5"> | 760 | <div> |
| 761 | <ProviderEnvironmentSection | 761 | <ProviderEnvironmentSection |
| 762 | environment={instance.environment ?? []} | 762 | environment={instance.environment ?? []} |
| 763 | onChange={updateEnvironment} | 763 | onChange={updateEnvironment} |
| ... | @@ -789,7 +789,7 @@ export function ProviderInstanceCard({ | ... | @@ -789,7 +789,7 @@ export function ProviderInstanceCard({ |
| 789 | onModelOrderChange={onModelOrderChange} | 789 | onModelOrderChange={onModelOrderChange} |
| 790 | /> | 790 | /> |
| 791 | ) : ( | 791 | ) : ( |
| 792 | <div className="border-t border-border/60 px-4 py-3 sm:px-5"> | 792 | <div> |
| 793 | <p className="text-xs text-muted-foreground"> | 793 | <p className="text-xs text-muted-foreground"> |
| 794 | This instance uses a driver ( | 794 | This instance uses a driver ( |
| 795 | <code className="text-foreground">{String(instance.driver)}</code>) that is not | 795 | <code className="text-foreground">{String(instance.driver)}</code>) that is not |
apps/web/src/components/settings/ProviderModelsSection.tsx+1-1| ... | @@ -185,7 +185,7 @@ export function ProviderModelsSection({ | ... | @@ -185,7 +185,7 @@ export function ProviderModelsSection({ |
| 185 | }; | 185 | }; |
| 186 | 186 | ||
| 187 | return ( | 187 | return ( |
| 188 | <div className="border-t border-border/60 px-4 py-3 sm:px-5"> | 188 | <div> |
| 189 | <div className="text-xs font-medium text-foreground">Models</div> | 189 | <div className="text-xs font-medium text-foreground">Models</div> |
| 190 | <div className="mt-1 text-xs text-muted-foreground"> | 190 | <div className="mt-1 text-xs text-muted-foreground"> |
| 191 | {models.length} model{models.length === 1 ? "" : "s"} available. | 191 | {models.length} model{models.length === 1 ? "" : "s"} available. |
apps/web/src/components/settings/ProviderSettingsForm.tsx+1-1| ... | @@ -167,7 +167,7 @@ function FieldFrame(props: { | ... | @@ -167,7 +167,7 @@ function FieldFrame(props: { |
| 167 | readonly children: ReactNode; | 167 | readonly children: ReactNode; |
| 168 | }) { | 168 | }) { |
| 169 | if (props.variant === "card") { | 169 | if (props.variant === "card") { |
| 170 | return <div className="border-t border-border/60 px-4 py-3 sm:px-5">{props.children}</div>; | 170 | return <div>{props.children}</div>; |
| 171 | } | 171 | } |
| 172 | return <div className="grid gap-1.5">{props.children}</div>; | 172 | return <div className="grid gap-1.5">{props.children}</div>; |
| 173 | } | 173 | } |
apps/web/src/components/settings/SettingsSidebarNav.tsx+4-7| ... | @@ -18,7 +18,6 @@ import { | ... | @@ -18,7 +18,6 @@ import { |
| 18 | SidebarMenu, | 18 | SidebarMenu, |
| 19 | SidebarMenuButton, | 19 | SidebarMenuButton, |
| 20 | SidebarMenuItem, | 20 | SidebarMenuItem, |
| 21 | SidebarSeparator, | ||
| 22 | useSidebar, | 21 | useSidebar, |
| 23 | } from "../ui/sidebar"; | 22 | } from "../ui/sidebar"; |
| 24 | import { T3ConnectSidebarAvatar, T3ConnectSidebarSignIn } from "../clerk/T3ConnectSidebarSignIn"; | 23 | import { T3ConnectSidebarAvatar, T3ConnectSidebarSignIn } from "../clerk/T3ConnectSidebarSignIn"; |
| ... | @@ -85,8 +84,8 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -85,8 +84,8 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 85 | isActive={isActive} | 84 | isActive={isActive} |
| 86 | className={ | 85 | className={ |
| 87 | isActive | 86 | isActive |
| 88 | ? "gap-2.5 px-2.5 py-2 text-left text-[13px] font-medium text-sidebar-foreground" | 87 | ? "h-8 items-center gap-2 rounded-md bg-sidebar-row-active px-2 py-1.5 text-left text-sm font-medium text-sidebar-foreground" |
| 89 | : "gap-2.5 px-2.5 py-2 text-left text-[13px] text-sidebar-muted-foreground hover:text-sidebar-foreground" | 88 | : "h-8 items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm font-medium text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 90 | } | 89 | } |
| 91 | onClick={() => handleSectionClick(item.to)} | 90 | onClick={() => handleSectionClick(item.to)} |
| 92 | > | 91 | > |
| ... | @@ -94,7 +93,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -94,7 +93,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 94 | className={ | 93 | className={ |
| 95 | isActive | 94 | isActive |
| 96 | ? "size-4 shrink-0 text-sidebar-foreground" | 95 | ? "size-4 shrink-0 text-sidebar-foreground" |
| 97 | : "size-4 shrink-0 text-sidebar-muted-foreground" | 96 | : "size-4 shrink-0 text-sidebar-muted-foreground/60" |
| 98 | } | 97 | } |
| 99 | /> | 98 | /> |
| 100 | <span className="truncate">{item.label}</span> | 99 | <span className="truncate">{item.label}</span> |
| ... | @@ -105,8 +104,6 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -105,8 +104,6 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 105 | </SidebarMenu> | 104 | </SidebarMenu> |
| 106 | </SidebarGroup> | 105 | </SidebarGroup> |
| 107 | </SidebarContent> | 106 | </SidebarContent> |
| 108 | |||
| 109 | <SidebarSeparator /> | ||
| 110 | <SidebarFooter className="p-2"> | 107 | <SidebarFooter className="p-2"> |
| 111 | <T3ConnectSidebarSignIn /> | 108 | <T3ConnectSidebarSignIn /> |
| 112 | <div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-1"> | 109 | <div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-1"> |
| ... | @@ -114,7 +111,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -114,7 +111,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 114 | <SidebarMenuItem> | 111 | <SidebarMenuItem> |
| 115 | <SidebarMenuButton | 112 | <SidebarMenuButton |
| 116 | size="sm" | 113 | size="sm" |
| 117 | className="gap-2 px-2 py-2 text-xs text-sidebar-muted-foreground hover:bg-sidebar-row-hover hover:text-sidebar-foreground" | 114 | className="h-8 items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 118 | onClick={handleBackClick} | 115 | onClick={handleBackClick} |
| 119 | > | 116 | > |
| 120 | <ArrowLeftIcon className="size-4" /> | 117 | <ArrowLeftIcon className="size-4" /> |
apps/web/src/components/settings/SourceControlSettings.tsx+6-6| ... | @@ -229,16 +229,16 @@ function DiscoveryItemRow({ | ... | @@ -229,16 +229,16 @@ function DiscoveryItemRow({ |
| 229 | return ( | 229 | return ( |
| 230 | <div | 230 | <div |
| 231 | className={cn( | 231 | className={cn( |
| 232 | "border-t border-border/60 first:border-t-0", | 232 | "rounded-xl transition-colors hover:bg-muted/20", |
| 233 | isVcsNotReady(item) && "opacity-80", | 233 | isVcsNotReady(item) && "opacity-80", |
| 234 | )} | 234 | )} |
| 235 | > | 235 | > |
| 236 | <div className="px-4 py-3.5 sm:px-5"> | 236 | <div className="px-3 py-3 sm:px-4"> |
| 237 | <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> | 237 | <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> |
| 238 | <div className="min-w-0 flex-1 space-y-1"> | 238 | <div className="min-w-0 flex-1 space-y-1"> |
| 239 | <div className="flex min-w-0 flex-wrap items-center gap-2"> | 239 | <div className="flex min-w-0 flex-wrap items-center gap-2"> |
| 240 | <SourceControlItemMark item={item} /> | 240 | <SourceControlItemMark item={item} /> |
| 241 | <span className="truncate text-[13px] font-semibold tracking-[-0.01em] text-foreground"> | 241 | <span className="truncate text-sm font-medium tracking-[-0.005em] text-foreground"> |
| 242 | {item.label} | 242 | {item.label} |
| 243 | </span> | 243 | </span> |
| 244 | {version ? <code className="text-xs text-muted-foreground">{version}</code> : null} | 244 | {version ? <code className="text-xs text-muted-foreground">{version}</code> : null} |
| ... | @@ -253,7 +253,7 @@ function DiscoveryItemRow({ | ... | @@ -253,7 +253,7 @@ function DiscoveryItemRow({ |
| 253 | </Badge> | 253 | </Badge> |
| 254 | ) : null} | 254 | ) : null} |
| 255 | </div> | 255 | </div> |
| 256 | <p className="flex min-w-0 flex-wrap items-center gap-x-1 text-xs text-muted-foreground/80"> | 256 | <p className="flex min-w-0 flex-wrap items-center gap-x-1 text-[13px] leading-[1.45] text-muted-foreground/80"> |
| 257 | {itemSummary({ item, auth, authAccount })} | 257 | {itemSummary({ item, auth, authAccount })} |
| 258 | </p> | 258 | </p> |
| 259 | </div> | 259 | </div> |
| ... | @@ -282,7 +282,7 @@ function DiscoveryItemRow({ | ... | @@ -282,7 +282,7 @@ function DiscoveryItemRow({ |
| 282 | {hasDetails ? ( | 282 | {hasDetails ? ( |
| 283 | <Collapsible open={isExpanded} onOpenChange={setIsExpanded}> | 283 | <Collapsible open={isExpanded} onOpenChange={setIsExpanded}> |
| 284 | <CollapsibleContent> | 284 | <CollapsibleContent> |
| 285 | <div className="border-t border-border/60 px-4 py-3 sm:px-5">{children}</div> | 285 | <div className="px-3 pb-4 pt-1 sm:px-4">{children}</div> |
| 286 | </CollapsibleContent> | 286 | </CollapsibleContent> |
| 287 | </Collapsible> | 287 | </Collapsible> |
| 288 | ) : null} | 288 | ) : null} |
| ... | @@ -368,7 +368,7 @@ function SourceControlSectionSkeleton({ | ... | @@ -368,7 +368,7 @@ function SourceControlSectionSkeleton({ |
| 368 | return ( | 368 | return ( |
| 369 | <SettingsSection title={title} headerAction={headerAction}> | 369 | <SettingsSection title={title} headerAction={headerAction}> |
| 370 | {SOURCE_CONTROL_SKELETON_ROWS.map((row) => ( | 370 | {SOURCE_CONTROL_SKELETON_ROWS.map((row) => ( |
| 371 | <div key={row} className="border-t border-border/60 px-4 py-3.5 first:border-t-0 sm:px-5"> | 371 | <div key={row} className="rounded-xl px-3 py-3 sm:px-4"> |
| 372 | <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> | 372 | <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> |
| 373 | <div className="min-w-0 flex-1 space-y-2"> | 373 | <div className="min-w-0 flex-1 space-y-2"> |
| 374 | <div className="flex items-center gap-2"> | 374 | <div className="flex items-center gap-2"> |
apps/web/src/components/settings/itemRows.ts+2-2| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | /** Direct row in the card – same pattern as the Provider / ACP-agent list rows. */ | 1 | /** Direct row in a settings section. Whitespace, rather than rules, separates peers. */ |
| 2 | export const ITEM_ROW_CLASSNAME = "border-t border-border/60 px-4 py-4 first:border-t-0 sm:px-5"; | 2 | export const ITEM_ROW_CLASSNAME = "rounded-xl px-3 py-3 sm:px-4"; |
| 3 | 3 | ||
| 4 | export const ITEM_ROW_INNER_CLASSNAME = | 4 | export const ITEM_ROW_INNER_CLASSNAME = |
| 5 | "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"; | 5 | "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"; |
apps/web/src/components/settings/settingsLayout.tsx+14-21| ... | @@ -29,18 +29,15 @@ export function SettingsSection({ | ... | @@ -29,18 +29,15 @@ export function SettingsSection({ |
| 29 | children: ReactNode; | 29 | children: ReactNode; |
| 30 | }) { | 30 | }) { |
| 31 | return ( | 31 | return ( |
| 32 | <section {...sectionProps} className={cn("space-y-2.5", className)}> | 32 | <section {...sectionProps} className={cn("space-y-3", className)}> |
| 33 | <div className="flex items-center justify-between px-1"> | 33 | <div className="flex min-h-8 items-center justify-between gap-4 px-3 sm:px-4"> |
| 34 | <h2 className="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-foreground/50"> | 34 | <h2 className="flex items-center gap-2 text-lg font-semibold tracking-[-0.025em] text-foreground"> |
| 35 | <span className="inline-block h-px w-3 bg-border" aria-hidden /> | ||
| 36 | {icon} | 35 | {icon} |
| 37 | {title} | 36 | {title} |
| 38 | </h2> | 37 | </h2> |
| 39 | <div className="flex h-5 min-w-5 items-center justify-end">{headerAction}</div> | 38 | <div className="flex min-h-7 min-w-7 items-center justify-end">{headerAction}</div> |
| 40 | </div> | ||
| 41 | <div className="relative overflow-visible rounded-2xl border bg-surface-raised text-card-foreground shadow-sm/4 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:shadow-none dark:before:shadow-[0_-1px_--theme(--color-white/6%)]"> | ||
| 42 | {children} | ||
| 43 | </div> | 39 | </div> |
| 40 | <div className="relative space-y-1 overflow-visible text-foreground">{children}</div> | ||
| 44 | </section> | 41 | </section> |
| 45 | ); | 42 | ); |
| 46 | } | 43 | } |
| ... | @@ -65,24 +62,20 @@ export function SettingsRow({ | ... | @@ -65,24 +62,20 @@ export function SettingsRow({ |
| 65 | return ( | 62 | return ( |
| 66 | <div | 63 | <div |
| 67 | {...rowProps} | 64 | {...rowProps} |
| 68 | className={cn( | 65 | className={cn("rounded-xl px-3 sm:px-4", children ? "pt-3 pb-1" : "py-3", className)} |
| 69 | "border-t border-border/60 px-4 first:border-t-0 sm:px-5", | ||
| 70 | children ? "pt-3.5 pb-0" : "py-3.5", | ||
| 71 | className, | ||
| 72 | )} | ||
| 73 | > | 66 | > |
| 74 | <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> | 67 | <div className="flex flex-col gap-3 sm:grid sm:grid-cols-[minmax(0,1fr)_minmax(10rem,auto)] sm:items-center sm:gap-8"> |
| 75 | <div className="min-w-0 flex-1 space-y-1"> | 68 | <div className="min-w-0 flex-1 space-y-1"> |
| 76 | <div className="flex min-h-5 items-center gap-1.5"> | 69 | <div className="flex min-h-5 items-center gap-1.5"> |
| 77 | <h3 className="text-[13px] font-semibold tracking-[-0.01em] text-foreground"> | 70 | <h3 className="text-sm font-medium tracking-[-0.005em] text-foreground">{title}</h3> |
| 78 | {title} | ||
| 79 | </h3> | ||
| 80 | <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center"> | 71 | <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center"> |
| 81 | {resetAction} | 72 | {resetAction} |
| 82 | </span> | 73 | </span> |
| 83 | </div> | 74 | </div> |
| 84 | <p className="text-xs text-muted-foreground/80">{description}</p> | 75 | <p className="max-w-xl text-[13px] leading-[1.45] text-muted-foreground/80"> |
| 85 | {status ? <div className="pt-0.5 text-[11px] text-muted-foreground">{status}</div> : null} | 76 | {description} |
| 77 | </p> | ||
| 78 | {status ? <div className="pt-0.5 text-xs text-muted-foreground">{status}</div> : null} | ||
| 86 | </div> | 79 | </div> |
| 87 | {control ? ( | 80 | {control ? ( |
| 88 | <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> | 81 | <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> |
| ... | @@ -127,8 +120,8 @@ export function SettingsPageContainer({ | ... | @@ -127,8 +120,8 @@ export function SettingsPageContainer({ |
| 127 | className?: string; | 120 | className?: string; |
| 128 | }) { | 121 | }) { |
| 129 | return ( | 122 | return ( |
| 130 | <div className="scrollbar-gutter-both flex-1 overflow-y-auto p-6 sm:p-8"> | 123 | <div className="scrollbar-gutter-both flex-1 overflow-y-auto px-4 py-7 sm:px-8 sm:py-10"> |
| 131 | <div className={cn("mx-auto flex w-full max-w-3xl flex-col gap-8", className)}> | 124 | <div className={cn("mx-auto flex w-full max-w-4xl flex-col gap-12", className)}> |
| 132 | {children} | 125 | {children} |
| 133 | </div> | 126 | </div> |
| 134 | </div> | 127 | </div> |
apps/web/src/components/sidebar/SidebarChrome.tsx+2-2| ... | @@ -115,10 +115,10 @@ export const SidebarChromeFooter = memo(function SidebarChromeFooter() { | ... | @@ -115,10 +115,10 @@ export const SidebarChromeFooter = memo(function SidebarChromeFooter() { |
| 115 | <SidebarMenuItem> | 115 | <SidebarMenuItem> |
| 116 | <SidebarMenuButton | 116 | <SidebarMenuButton |
| 117 | size="sm" | 117 | size="sm" |
| 118 | className="gap-2 px-2 py-1.5 text-sm font-medium text-sidebar-muted-foreground hover:bg-sidebar-row-hover hover:text-sidebar-foreground" | 118 | className="h-8 items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 119 | onClick={handleSettingsClick} | 119 | onClick={handleSettingsClick} |
| 120 | > | 120 | > |
| 121 | <SettingsIcon className="size-4 shrink-0" /> | 121 | <SettingsIcon className="size-4.5 shrink-0" /> |
| 122 | <span>Settings</span> | 122 | <span>Settings</span> |
| 123 | </SidebarMenuButton> | 123 | </SidebarMenuButton> |
| 124 | </SidebarMenuItem> | 124 | </SidebarMenuItem> |
apps/web/src/components/ui/button.tsx+1-1| ... | @@ -40,7 +40,7 @@ const buttonVariants = cva( | ... | @@ -40,7 +40,7 @@ const buttonVariants = cva( |
| 40 | "border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [&_svg:not([class*='text-'])]:text-muted-foreground", | 40 | "border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [&_svg:not([class*='text-'])]:text-muted-foreground", |
| 41 | link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline", | 41 | link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline", |
| 42 | outline: | 42 | outline: |
| 43 | "border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64 [&_svg:not([class*='text-'])]:text-muted-foreground", | 43 | "border-transparent bg-popover not-dark:bg-clip-padding text-foreground shadow-none dark:bg-input/32 [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64 [&_svg:not([class*='text-'])]:text-muted-foreground", |
| 44 | secondary: | 44 | secondary: |
| 45 | "border-transparent bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90", | 45 | "border-transparent bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90", |
| 46 | }, | 46 | }, |
apps/web/src/components/ui/combobox.tsx+5-9| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | "use client"; | 1 | "use client"; |
| 2 | 2 | ||
| 3 | import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox"; | 3 | import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox"; |
| 4 | import { CheckIcon, ChevronsUpDownIcon, XIcon } from "lucide-react"; | 4 | import { ChevronsUpDownIcon, XIcon } from "lucide-react"; |
| 5 | import * as React from "react"; | 5 | import * as React from "react"; |
| 6 | 6 | ||
| 7 | import { cn } from "~/lib/utils"; | 7 | import { cn } from "~/lib/utils"; |
| ... | @@ -170,7 +170,7 @@ function ComboboxPopup({ | ... | @@ -170,7 +170,7 @@ function ComboboxPopup({ |
| 170 | > | 170 | > |
| 171 | <span | 171 | <span |
| 172 | className={cn( | 172 | className={cn( |
| 173 | "relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | 173 | "dropdown-glass relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg transition-[scale,opacity]", |
| 174 | className, | 174 | className, |
| 175 | )} | 175 | )} |
| 176 | > | 176 | > |
| ... | @@ -191,7 +191,7 @@ function ComboboxItem({ | ... | @@ -191,7 +191,7 @@ function ComboboxItem({ |
| 191 | className, | 191 | className, |
| 192 | contentClassName, | 192 | contentClassName, |
| 193 | children, | 193 | children, |
| 194 | hideIndicator = false, | 194 | hideIndicator: _hideIndicator = false, |
| 195 | ...props | 195 | ...props |
| 196 | }: ComboboxPrimitive.Item.Props & { | 196 | }: ComboboxPrimitive.Item.Props & { |
| 197 | contentClassName?: string; | 197 | contentClassName?: string; |
| ... | @@ -200,19 +200,15 @@ function ComboboxItem({ | ... | @@ -200,19 +200,15 @@ function ComboboxItem({ |
| 200 | return ( | 200 | return ( |
| 201 | <ComboboxPrimitive.Item | 201 | <ComboboxPrimitive.Item |
| 202 | className={cn( | 202 | className={cn( |
| 203 | "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none hover:bg-accent data-disabled:pointer-events-none data-selected:bg-accent/50 data-selected:text-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground [&[data-highlighted][data-selected]]:bg-accent [&[data-highlighted][data-selected]]:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | 203 | "flex min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center rounded-sm px-2 py-1 text-base outline-none hover:bg-accent data-disabled:pointer-events-none data-selected:bg-foreground/[0.08] data-selected:text-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground [&[data-highlighted][data-selected]]:bg-accent [&[data-highlighted][data-selected]]:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", |
| 204 | className, | 204 | className, |
| 205 | )} | 205 | )} |
| 206 | data-slot="combobox-item" | 206 | data-slot="combobox-item" |
| 207 | {...props} | 207 | {...props} |
| 208 | > | 208 | > |
| 209 | <ComboboxPrimitive.ItemIndicator className={cn("col-start-1", hideIndicator && "hidden")}> | ||
| 210 | <CheckIcon /> | ||
| 211 | </ComboboxPrimitive.ItemIndicator> | ||
| 212 | <div | 209 | <div |
| 213 | className={cn( | 210 | className={cn( |
| 214 | "[&_svg:not([class*='text-'])]:text-muted-foreground", | 211 | "min-w-0 flex-1 [&_svg:not([class*='text-'])]:text-muted-foreground", |
| 215 | hideIndicator ? "col-start-1 col-span-full" : "col-start-2", | ||
| 216 | contentClassName, | 212 | contentClassName, |
| 217 | )} | 213 | )} |
| 218 | data-slot="combobox-item-content" | 214 | data-slot="combobox-item-content" |
apps/web/src/components/ui/command.tsx+12-5| ... | @@ -30,7 +30,7 @@ function CommandDialogBackdrop({ className, ...props }: CommandDialogPrimitive.B | ... | @@ -30,7 +30,7 @@ function CommandDialogBackdrop({ className, ...props }: CommandDialogPrimitive.B |
| 30 | return ( | 30 | return ( |
| 31 | <CommandDialogPrimitive.Backdrop | 31 | <CommandDialogPrimitive.Backdrop |
| 32 | className={cn( | 32 | className={cn( |
| 33 | "fixed inset-0 z-50 bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | 33 | "fixed inset-0 z-50 bg-black/40 transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", |
| 34 | className, | 34 | className, |
| 35 | )} | 35 | )} |
| 36 | data-slot="command-dialog-backdrop" | 36 | data-slot="command-dialog-backdrop" |
| ... | @@ -66,7 +66,7 @@ function CommandDialogPopup({ | ... | @@ -66,7 +66,7 @@ function CommandDialogPopup({ |
| 66 | <CommandDialogViewport> | 66 | <CommandDialogViewport> |
| 67 | <CommandDialogPrimitive.Popup | 67 | <CommandDialogPrimitive.Popup |
| 68 | className={cn( | 68 | className={cn( |
| 69 | "pointer-events-auto -translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-105 min-h-0 w-full min-w-0 max-w-xl scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=scroll-area-viewport]:data-has-overflow-y:pe-1 dark:border-transparent dark:shadow-none dark:inset-ring-1 dark:inset-ring-white/5 dark:before:shadow-none", | 69 | "chat-composer-glass command-palette-glass pointer-events-auto -translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-105 min-h-0 w-full min-w-0 max-w-xl scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl text-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=scroll-area-viewport]:data-has-overflow-y:pe-1", |
| 70 | className, | 70 | className, |
| 71 | )} | 71 | )} |
| 72 | data-slot="command-dialog-popup" | 72 | data-slot="command-dialog-popup" |
| ... | @@ -144,7 +144,7 @@ function CommandPanel({ className, ...props }: React.ComponentProps<"div">) { | ... | @@ -144,7 +144,7 @@ function CommandPanel({ className, ...props }: React.ComponentProps<"div">) { |
| 144 | return ( | 144 | return ( |
| 145 | <div | 145 | <div |
| 146 | className={cn( | 146 | className={cn( |
| 147 | "-mx-px not-has-[+[data-slot=command-footer]]:-mb-px relative min-h-0 overflow-hidden rounded-t-xl not-has-[+[data-slot=command-footer]]:rounded-b-2xl border border-b-0 bg-popover bg-clip-padding shadow-xs/5 [clip-path:inset(0_1px)] not-has-[+[data-slot=command-footer]]:[clip-path:inset(0_1px_1px_1px_round_0_0_calc(var(--radius-2xl)-1px)_calc(var(--radius-2xl)-1px))] before:pointer-events-none before:absolute before:inset-0 before:rounded-t-[calc(var(--radius-xl)-1px)] **:data-[slot=scroll-area-scrollbar]:mt-2 [touch-action:pan-y] dark:border-white/5 dark:shadow-none", | 147 | "relative min-h-0 overflow-hidden rounded-t-xl not-has-[+[data-slot=command-footer]]:rounded-b-2xl bg-transparent **:data-[slot=scroll-area-scrollbar]:mt-2 [touch-action:pan-y]", |
| 148 | className, | 148 | className, |
| 149 | )} | 149 | )} |
| 150 | {...props} | 150 | {...props} |
| ... | @@ -171,7 +171,14 @@ function CommandCollection({ ...props }: React.ComponentProps<typeof Autocomplet | ... | @@ -171,7 +171,14 @@ function CommandCollection({ ...props }: React.ComponentProps<typeof Autocomplet |
| 171 | 171 | ||
| 172 | function CommandItem({ className, ...props }: React.ComponentProps<typeof AutocompleteItem>) { | 172 | function CommandItem({ className, ...props }: React.ComponentProps<typeof AutocompleteItem>) { |
| 173 | return ( | 173 | return ( |
| 174 | <AutocompleteItem className={cn("py-1.5", className)} data-slot="command-item" {...props} /> | 174 | <AutocompleteItem |
| 175 | className={cn( | ||
| 176 | "py-1.5 data-selected:bg-foreground/[0.06] data-highlighted:bg-foreground/[0.09] data-highlighted:text-foreground [&[data-highlighted][data-selected]]:bg-foreground/[0.09] [&[data-highlighted][data-selected]]:text-foreground", | ||
| 177 | className, | ||
| 178 | )} | ||
| 179 | data-slot="command-item" | ||
| 180 | {...props} | ||
| 181 | /> | ||
| 175 | ); | 182 | ); |
| 176 | } | 183 | } |
| 177 | 184 | ||
| ... | @@ -205,7 +212,7 @@ function CommandFooter({ className, ...props }: React.ComponentProps<"div">) { | ... | @@ -205,7 +212,7 @@ function CommandFooter({ className, ...props }: React.ComponentProps<"div">) { |
| 205 | return ( | 212 | return ( |
| 206 | <div | 213 | <div |
| 207 | className={cn( | 214 | className={cn( |
| 208 | "relative flex items-center justify-between gap-2 rounded-b-[calc(var(--radius-2xl)-1px)] border-t border-zinc-200/80 px-5 py-3 font-medium text-sm text-zinc-700 dark:border-white/8 dark:text-zinc-300 [&_[data-slot=kbd-group]]:font-sans [&_[data-slot=kbd]]:bg-zinc-100 [&_[data-slot=kbd]]:text-zinc-900 [&_[data-slot=kbd]]:ring-1 [&_[data-slot=kbd]]:ring-zinc-200/80 [&_[data-slot=kbd]]:dark:bg-zinc-800 [&_[data-slot=kbd]]:dark:text-zinc-100 [&_[data-slot=kbd]]:dark:ring-zinc-700", | 215 | "relative flex items-center justify-between gap-2 rounded-b-[calc(var(--radius-2xl)-1px)] bg-foreground/[0.025] px-5 py-3 font-medium text-sm text-muted-foreground [&_[data-slot=kbd-group]]:font-sans [&_[data-slot=kbd]]:bg-foreground/[0.08] [&_[data-slot=kbd]]:text-foreground [&_[data-slot=kbd]]:ring-0", |
| 209 | className, | 216 | className, |
| 210 | )} | 217 | )} |
| 211 | data-slot="command-footer" | 218 | data-slot="command-footer" |
apps/web/src/components/ui/menu.tsx+4-22| ... | @@ -49,7 +49,7 @@ function MenuPopup({ | ... | @@ -49,7 +49,7 @@ function MenuPopup({ |
| 49 | > | 49 | > |
| 50 | <MenuPrimitive.Popup | 50 | <MenuPrimitive.Popup |
| 51 | className={cn( | 51 | className={cn( |
| 52 | "relative flex not-[class*='w-']:min-w-32 origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 outline-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] focus:outline-none dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | 52 | "dropdown-glass relative flex not-[class*='w-']:min-w-32 origin-(--transform-origin) rounded-lg outline-none focus:outline-none", |
| 53 | className, | 53 | className, |
| 54 | )} | 54 | )} |
| 55 | data-slot="menu-popup" | 55 | data-slot="menu-popup" |
| ... | @@ -150,7 +150,7 @@ function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) { | ... | @@ -150,7 +150,7 @@ function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) { |
| 150 | function MenuRadioItem({ | 150 | function MenuRadioItem({ |
| 151 | className, | 151 | className, |
| 152 | children, | 152 | children, |
| 153 | hideIndicator = false, | 153 | hideIndicator: _hideIndicator = false, |
| 154 | ...props | 154 | ...props |
| 155 | }: MenuPrimitive.RadioItem.Props & { | 155 | }: MenuPrimitive.RadioItem.Props & { |
| 156 | hideIndicator?: boolean; | 156 | hideIndicator?: boolean; |
| ... | @@ -158,31 +158,13 @@ function MenuRadioItem({ | ... | @@ -158,31 +158,13 @@ function MenuRadioItem({ |
| 158 | return ( | 158 | return ( |
| 159 | <MenuPrimitive.RadioItem | 159 | <MenuPrimitive.RadioItem |
| 160 | className={cn( | 160 | className={cn( |
| 161 | "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | 161 | "flex min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center rounded-sm px-2 py-1 text-base text-foreground outline-none data-checked:bg-foreground/[0.08] data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", |
| 162 | hideIndicator ? "grid-cols-[1fr] ps-3 pe-3" : "grid-cols-[1rem_1fr] ps-2 pe-4", | ||
| 163 | className, | 162 | className, |
| 164 | )} | 163 | )} |
| 165 | data-slot="menu-radio-item" | 164 | data-slot="menu-radio-item" |
| 166 | {...props} | 165 | {...props} |
| 167 | > | 166 | > |
| 168 | {hideIndicator ? null : ( | 167 | <span className="min-w-0 flex-1">{children}</span> |
| 169 | <MenuPrimitive.RadioItemIndicator className="col-start-1"> | ||
| 170 | <svg | ||
| 171 | fill="none" | ||
| 172 | height="24" | ||
| 173 | stroke="currentColor" | ||
| 174 | strokeLinecap="round" | ||
| 175 | strokeLinejoin="round" | ||
| 176 | strokeWidth="2" | ||
| 177 | viewBox="0 0 24 24" | ||
| 178 | width="24" | ||
| 179 | xmlns="http://www.w3.org/2000/svg" | ||
| 180 | > | ||
| 181 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | ||
| 182 | </svg> | ||
| 183 | </MenuPrimitive.RadioItemIndicator> | ||
| 184 | )} | ||
| 185 | <span className={hideIndicator ? "col-start-1" : "col-start-2"}>{children}</span> | ||
| 186 | </MenuPrimitive.RadioItem> | 168 | </MenuPrimitive.RadioItem> |
| 187 | ); | 169 | ); |
| 188 | } | 170 | } |
apps/web/src/components/ui/select.tsx+4-25| ... | @@ -150,7 +150,7 @@ function SelectPopup({ | ... | @@ -150,7 +150,7 @@ function SelectPopup({ |
| 150 | </SelectPrimitive.ScrollUpArrow> | 150 | </SelectPrimitive.ScrollUpArrow> |
| 151 | <div | 151 | <div |
| 152 | className={cn( | 152 | className={cn( |
| 153 | "relative h-full rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | 153 | "dropdown-glass relative h-full rounded-lg", |
| 154 | matchTriggerWidth && "min-w-(--anchor-width)", | 154 | matchTriggerWidth && "min-w-(--anchor-width)", |
| 155 | popupClassName, | 155 | popupClassName, |
| 156 | )} | 156 | )} |
| ... | @@ -177,7 +177,7 @@ function SelectPopup({ | ... | @@ -177,7 +177,7 @@ function SelectPopup({ |
| 177 | function SelectItem({ | 177 | function SelectItem({ |
| 178 | className, | 178 | className, |
| 179 | children, | 179 | children, |
| 180 | hideIndicator = false, | 180 | hideIndicator: _hideIndicator = false, |
| 181 | ...props | 181 | ...props |
| 182 | }: SelectPrimitive.Item.Props & { | 182 | }: SelectPrimitive.Item.Props & { |
| 183 | hideIndicator?: boolean; | 183 | hideIndicator?: boolean; |
| ... | @@ -185,35 +185,14 @@ function SelectItem({ | ... | @@ -185,35 +185,14 @@ function SelectItem({ |
| 185 | return ( | 185 | return ( |
| 186 | <SelectPrimitive.Item | 186 | <SelectPrimitive.Item |
| 187 | className={cn( | 187 | className={cn( |
| 188 | "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | 188 | "flex min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center rounded-sm px-2 py-1 text-base outline-none data-selected:bg-foreground/[0.08] data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", |
| 189 | hideIndicator ? "grid-cols-[1fr] ps-3 pe-3" : "grid-cols-[1rem_1fr] ps-2 pe-4", | ||
| 190 | className, | 189 | className, |
| 191 | )} | 190 | )} |
| 192 | data-slot="select-item" | 191 | data-slot="select-item" |
| 193 | {...props} | 192 | {...props} |
| 194 | > | 193 | > |
| 195 | {hideIndicator ? null : ( | ||
| 196 | <SelectPrimitive.ItemIndicator className="col-start-1" data-slot="select-item-indicator"> | ||
| 197 | <svg | ||
| 198 | fill="none" | ||
| 199 | height="24" | ||
| 200 | stroke="currentColor" | ||
| 201 | strokeLinecap="round" | ||
| 202 | strokeLinejoin="round" | ||
| 203 | strokeWidth="2" | ||
| 204 | viewBox="0 0 24 24" | ||
| 205 | width="24" | ||
| 206 | xmlns="http://www.w3.org/1500/svg" | ||
| 207 | > | ||
| 208 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | ||
| 209 | </svg> | ||
| 210 | </SelectPrimitive.ItemIndicator> | ||
| 211 | )} | ||
| 212 | <SelectPrimitive.ItemText | 194 | <SelectPrimitive.ItemText |
| 213 | className={cn( | 195 | className="min-w-0 flex-1 [&_svg:not([class*='text-'])]:text-muted-foreground" |
| 214 | "min-w-0 [&_svg:not([class*='text-'])]:text-muted-foreground", | ||
| 215 | hideIndicator ? "col-start-1" : "col-start-2", | ||
| 216 | )} | ||
| 217 | data-slot="select-item-text" | 196 | data-slot="select-item-text" |
| 218 | > | 197 | > |
| 219 | {children} | 198 | {children} |
apps/web/src/index.css+183-75| ... | @@ -107,7 +107,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -107,7 +107,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | @theme inline { | 109 | @theme inline { |
| 110 | --color-zinc-25: oklch(99.2% 0 0); | ||
| 111 | --animate-skeleton: skeleton 2s -1s infinite linear; | 110 | --animate-skeleton: skeleton 2s -1s infinite linear; |
| 112 | /* Duty-cycled indicator animations: long holds with stepped ramps, so the | 111 | /* Duty-cycled indicator animations: long holds with stepped ramps, so the |
| 113 | compositor updates discrete frames instead of every vsync. */ | 112 | compositor updates discrete frames instead of every vsync. */ |
| ... | @@ -303,6 +302,46 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -303,6 +302,46 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 303 | align-items: center; | 302 | align-items: center; |
| 304 | } | 303 | } |
| 305 | 304 | ||
| 305 | /* Fade rows themselves as they pass beneath the top chrome. A mask remains | ||
| 306 | visible even when the header and timeline share the same background. */ | ||
| 307 | .chat-timeline-scroll-fade { | ||
| 308 | --timeline-top-fade-height: 2.5rem; | ||
| 309 | -webkit-mask-image: | ||
| 310 | linear-gradient( | ||
| 311 | to bottom, | ||
| 312 | transparent 0%, | ||
| 313 | rgb(0 0 0 / 10%) 10%, | ||
| 314 | rgb(0 0 0 / 30%) 24%, | ||
| 315 | rgb(0 0 0 / 58%) 42%, | ||
| 316 | rgb(0 0 0 / 82%) 62%, | ||
| 317 | rgb(0 0 0 / 96%) 82%, | ||
| 318 | black 100% | ||
| 319 | ), | ||
| 320 | linear-gradient(black, black); | ||
| 321 | -webkit-mask-position: top, bottom; | ||
| 322 | -webkit-mask-repeat: no-repeat; | ||
| 323 | -webkit-mask-size: | ||
| 324 | 100% var(--timeline-top-fade-height), | ||
| 325 | 100% calc(100% - var(--timeline-top-fade-height)); | ||
| 326 | mask-image: | ||
| 327 | linear-gradient( | ||
| 328 | to bottom, | ||
| 329 | transparent 0%, | ||
| 330 | rgb(0 0 0 / 10%) 10%, | ||
| 331 | rgb(0 0 0 / 30%) 24%, | ||
| 332 | rgb(0 0 0 / 58%) 42%, | ||
| 333 | rgb(0 0 0 / 82%) 62%, | ||
| 334 | rgb(0 0 0 / 96%) 82%, | ||
| 335 | black 100% | ||
| 336 | ), | ||
| 337 | linear-gradient(black, black); | ||
| 338 | mask-position: top, bottom; | ||
| 339 | mask-repeat: no-repeat; | ||
| 340 | mask-size: | ||
| 341 | 100% var(--timeline-top-fade-height), | ||
| 342 | 100% calc(100% - var(--timeline-top-fade-height)); | ||
| 343 | } | ||
| 344 | |||
| 306 | .workspace-titlebar-controls { | 345 | .workspace-titlebar-controls { |
| 307 | position: absolute; | 346 | position: absolute; |
| 308 | top: var(--workspace-controls-top); | 347 | top: var(--workspace-controls-top); |
| ... | @@ -323,50 +362,75 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -323,50 +362,75 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 323 | } | 362 | } |
| 324 | 363 | ||
| 325 | .chat-composer-glass { | 364 | .chat-composer-glass { |
| 326 | background: var(--surface-raised); | 365 | background: var(--secondary); |
| 366 | -webkit-backdrop-filter: blur(16px); | ||
| 367 | backdrop-filter: blur(16px); | ||
| 327 | } | 368 | } |
| 328 | 369 | ||
| 329 | /* Mix from --background (not --card): the lower chrome is a full-width | 370 | .chat-composer-context-strip { |
| 330 | strip over the main view, and a card-tinted wash reads as a lighter | 371 | position: relative; |
| 331 | seam against it when no content is scrolled underneath. The backdrop | 372 | isolation: isolate; |
| 332 | blur, not the tint, carries the frosted effect. */ | ||
| 333 | .chat-composer-lower-chrome { | ||
| 334 | background: color-mix(in srgb, var(--background) 20%, transparent); | ||
| 335 | } | 373 | } |
| 336 | 374 | ||
| 337 | /* The translucent lower chrome lets the composer's shadow taper naturally | 375 | .chat-composer-context-strip::before { |
| 338 | into the message canvas. Above the terminal drawer that same shadow reads | 376 | position: absolute; |
| 339 | as a dirty light-to-black gradient, so end the workspace on an opaque | 377 | z-index: -1; |
| 340 | surface and let the drawer's border define the boundary instead. */ | 378 | inset: 0; |
| 341 | .chat-composer-lower-chrome[data-terminal-open="true"] { | 379 | border-radius: 0 0 16px 16px; |
| 342 | background: var(--background); | 380 | background: color-mix(in srgb, var(--card) 20%, transparent); |
| 381 | box-shadow: 0 12px 28px -18px rgb(0 0 0 / 40%); | ||
| 382 | content: ""; | ||
| 383 | -webkit-backdrop-filter: blur(16px); | ||
| 384 | backdrop-filter: blur(16px); | ||
| 343 | } | 385 | } |
| 344 | 386 | ||
| 345 | .chat-composer-glass { | 387 | .dropdown-glass { |
| 346 | box-shadow: | 388 | background: var(--secondary); |
| 347 | 0 18px 48px -20px rgb(0 0 0 / 28%), | 389 | -webkit-backdrop-filter: blur(16px); |
| 348 | 0 4px 14px -7px rgb(0 0 0 / 22%); | 390 | backdrop-filter: blur(16px); |
| 391 | box-shadow: 0 16px 40px -18px rgb(0 0 0 / 55%); | ||
| 392 | } | ||
| 393 | |||
| 394 | .command-palette-glass { | ||
| 395 | box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%); | ||
| 349 | } | 396 | } |
| 350 | 397 | ||
| 351 | .chat-composer-shared-blur { | 398 | .command-menu-surface { |
| 399 | background: var(--popover); | ||
| 400 | -webkit-backdrop-filter: none; | ||
| 401 | backdrop-filter: none; | ||
| 402 | box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%); | ||
| 403 | } | ||
| 404 | |||
| 405 | .workflow-dialog-surface { | ||
| 406 | border-color: transparent; | ||
| 407 | background: color-mix(in srgb, var(--popover) 82%, transparent); | ||
| 352 | -webkit-backdrop-filter: blur(16px); | 408 | -webkit-backdrop-filter: blur(16px); |
| 353 | backdrop-filter: blur(16px); | 409 | backdrop-filter: blur(16px); |
| 410 | box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%); | ||
| 354 | } | 411 | } |
| 355 | 412 | ||
| 356 | .dark .chat-composer-glass { | 413 | .dark .dropdown-glass { |
| 357 | background: var(--surface-raised); | 414 | box-shadow: 0 18px 44px -18px rgb(0 0 0 / 80%); |
| 358 | } | 415 | } |
| 359 | 416 | ||
| 360 | .dark .chat-composer-lower-chrome { | 417 | .dark .model-picker-surface.model-picker-surface { |
| 361 | background: color-mix(in srgb, var(--background) 45%, transparent); | 418 | background: color-mix(in srgb, var(--background) 90%, transparent); |
| 362 | } | 419 | } |
| 363 | 420 | ||
| 364 | .dark .chat-composer-lower-chrome[data-terminal-open="true"] { | 421 | .dark .command-menu-surface, |
| 365 | background: var(--background); | 422 | .dark .command-palette-glass, |
| 423 | .dark .workflow-dialog-surface { | ||
| 424 | box-shadow: none; | ||
| 366 | } | 425 | } |
| 367 | 426 | ||
| 368 | .dark .chat-composer-glass { | 427 | .dark .workflow-dialog-surface { |
| 369 | box-shadow: none; | 428 | background: color-mix(in srgb, var(--popover) 76%, transparent); |
| 429 | } | ||
| 430 | |||
| 431 | .dark .chat-composer-context-strip::before { | ||
| 432 | background: color-mix(in srgb, var(--card) 45%, transparent); | ||
| 433 | box-shadow: 0 14px 32px -18px rgb(0 0 0 / 75%); | ||
| 370 | } | 434 | } |
| 371 | 435 | ||
| 372 | .chat-composer-lower-chrome { | 436 | .chat-composer-lower-chrome { |
| ... | @@ -376,6 +440,10 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -376,6 +440,10 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 376 | } | 440 | } |
| 377 | 441 | ||
| 378 | @media (min-width: 40rem) { | 442 | @media (min-width: 40rem) { |
| 443 | .chat-timeline-scroll-fade { | ||
| 444 | --timeline-top-fade-height: 3rem; | ||
| 445 | } | ||
| 446 | |||
| 379 | .chat-composer-horizontal-inset { | 447 | .chat-composer-horizontal-inset { |
| 380 | padding-inline-start: calc(env(safe-area-inset-left) + 1.25rem); | 448 | padding-inline-start: calc(env(safe-area-inset-left) + 1.25rem); |
| 381 | padding-inline-end: calc(env(safe-area-inset-right) + 1.25rem); | 449 | padding-inline-end: calc(env(safe-area-inset-right) + 1.25rem); |
| ... | @@ -384,11 +452,19 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -384,11 +452,19 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 384 | 452 | ||
| 385 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { | 453 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { |
| 386 | .chat-composer-glass { | 454 | .chat-composer-glass { |
| 455 | background: var(--secondary); | ||
| 456 | } | ||
| 457 | |||
| 458 | .chat-composer-context-strip::before { | ||
| 387 | background: var(--card); | 459 | background: var(--card); |
| 388 | } | 460 | } |
| 389 | 461 | ||
| 390 | .chat-composer-lower-chrome { | 462 | .dropdown-glass { |
| 391 | background: var(--background); | 463 | background: color-mix(in srgb, var(--popover) 88%, black); |
| 464 | } | ||
| 465 | |||
| 466 | .workflow-dialog-surface { | ||
| 467 | background: var(--popover); | ||
| 392 | } | 468 | } |
| 393 | } | 469 | } |
| 394 | } | 470 | } |
| ... | @@ -430,25 +506,25 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -430,25 +506,25 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 430 | :root { | 506 | :root { |
| 431 | color-scheme: light; | 507 | color-scheme: light; |
| 432 | --radius: 0.625rem; | 508 | --radius: 0.625rem; |
| 433 | --background: var(--color-zinc-25); | 509 | --background: var(--color-white); |
| 434 | --app-chrome-background: var(--background); | 510 | --app-chrome-background: var(--background); |
| 435 | --surface-raised: color-mix(in srgb, var(--card) 20%, transparent); | 511 | --surface-raised: var(--secondary); |
| 436 | --foreground: var(--color-zinc-800); | 512 | --foreground: var(--color-neutral-800); |
| 437 | --card: var(--color-white); | 513 | --card: var(--color-white); |
| 438 | --card-foreground: var(--color-zinc-800); | 514 | --card-foreground: var(--color-neutral-800); |
| 439 | --popover: var(--color-white); | 515 | --popover: var(--color-white); |
| 440 | --popover-foreground: var(--color-zinc-800); | 516 | --popover-foreground: var(--color-neutral-800); |
| 441 | --primary: oklch(0.488 0.217 264); | 517 | --primary: oklch(0.488 0.217 264); |
| 442 | --primary-foreground: var(--color-white); | 518 | --primary-foreground: var(--color-white); |
| 443 | --secondary: var(--color-zinc-50); | 519 | --secondary: --alpha(var(--color-black) / 4%); |
| 444 | --secondary-foreground: var(--color-zinc-800); | 520 | --secondary-foreground: var(--color-neutral-800); |
| 445 | --muted: var(--color-zinc-50); | 521 | --muted: --alpha(var(--color-black) / 4%); |
| 446 | --muted-foreground: var(--color-zinc-500); | 522 | --muted-foreground: color-mix(in srgb, var(--color-neutral-500) 90%, var(--color-black)); |
| 447 | --accent: var(--color-zinc-100); | 523 | --accent: --alpha(var(--color-black) / 4%); |
| 448 | --accent-foreground: var(--color-zinc-900); | 524 | --accent-foreground: var(--color-neutral-800); |
| 449 | --destructive: var(--color-red-500); | 525 | --destructive: var(--color-red-500); |
| 450 | --border: var(--color-zinc-200); | 526 | --border: --alpha(var(--color-black) / 8%); |
| 451 | --input: var(--color-zinc-300); | 527 | --input: --alpha(var(--color-black) / 10%); |
| 452 | --ring: oklch(0.488 0.217 264); | 528 | --ring: oklch(0.488 0.217 264); |
| 453 | --destructive-foreground: var(--color-red-700); | 529 | --destructive-foreground: var(--color-red-700); |
| 454 | --info: var(--color-blue-500); | 530 | --info: var(--color-blue-500); |
| ... | @@ -470,22 +546,24 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -470,22 +546,24 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 470 | 546 | ||
| 471 | @variant dark { | 547 | @variant dark { |
| 472 | color-scheme: dark; | 548 | color-scheme: dark; |
| 473 | --background: var(--color-zinc-950); | 549 | /* Keep the workspace in the same neutral-black family as sidebar v2. |
| 550 | Surfaces lift from this base instead of starting from a milky gray. */ | ||
| 551 | --background: var(--color-neutral-950); | ||
| 474 | --app-chrome-background: var(--background); | 552 | --app-chrome-background: var(--background); |
| 475 | --surface-raised: color-mix(in srgb, var(--card) 45%, transparent); | 553 | --surface-raised: var(--secondary); |
| 476 | --foreground: var(--color-zinc-100); | 554 | --foreground: var(--color-neutral-100); |
| 477 | --card: var(--color-zinc-900); | 555 | --card: color-mix(in srgb, var(--background) 97%, var(--color-white)); |
| 478 | --card-foreground: var(--color-zinc-100); | 556 | --card-foreground: var(--color-neutral-100); |
| 479 | --popover: var(--color-zinc-900); | 557 | --popover: color-mix(in srgb, var(--background) 94%, var(--color-white)); |
| 480 | --popover-foreground: var(--color-zinc-100); | 558 | --popover-foreground: var(--color-neutral-100); |
| 481 | --primary: oklch(0.588 0.217 264); | 559 | --primary: oklch(0.588 0.217 264); |
| 482 | --primary-foreground: var(--color-white); | 560 | --primary-foreground: var(--color-white); |
| 483 | --secondary: var(--color-zinc-900); | 561 | --secondary: --alpha(var(--color-white) / 4%); |
| 484 | --secondary-foreground: var(--color-zinc-100); | 562 | --secondary-foreground: var(--color-neutral-100); |
| 485 | --muted: var(--color-zinc-900); | 563 | --muted: --alpha(var(--color-white) / 4%); |
| 486 | --muted-foreground: var(--color-zinc-400); | 564 | --muted-foreground: color-mix(in srgb, var(--color-neutral-500) 90%, var(--color-white)); |
| 487 | --accent: var(--color-zinc-900); | 565 | --accent: --alpha(var(--color-white) / 4%); |
| 488 | --accent-foreground: var(--color-zinc-100); | 566 | --accent-foreground: var(--color-neutral-100); |
| 489 | --destructive: color-mix(in srgb, var(--color-red-500) 90%, var(--color-white)); | 567 | --destructive: color-mix(in srgb, var(--color-red-500) 90%, var(--color-white)); |
| 490 | --border: --alpha(var(--color-white) / 6%); | 568 | --border: --alpha(var(--color-white) / 6%); |
| 491 | --input: --alpha(var(--color-white) / 8%); | 569 | --input: --alpha(var(--color-white) / 8%); |
| ... | @@ -500,27 +578,57 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -500,27 +578,57 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 500 | } | 578 | } |
| 501 | } | 579 | } |
| 502 | 580 | ||
| 503 | [data-app-sidebar] { | 581 | /* Give both navigation implementations the same cool graphite surface. The |
| 504 | --sidebar: var(--color-zinc-50); | 582 | version attribute remains useful for layout-specific styling, but color is |
| 505 | --sidebar-foreground: var(--color-zinc-800); | 583 | shared so switching the beta does not switch visual systems. */ |
| 506 | --sidebar-muted-foreground: var(--color-zinc-500); | 584 | [data-sidebar-version="v1"], |
| 507 | --sidebar-control-surface: var(--color-zinc-100); | 585 | [data-sidebar-version="v2"] { |
| 508 | --sidebar-row-hover: var(--color-zinc-25); | 586 | --background: oklch(0.965 0.009 265); |
| 509 | --sidebar-row-active: var(--color-white); | 587 | --foreground: oklch(0.27 0.025 265); |
| 510 | --sidebar-row-selected: var(--color-white); | 588 | --card: oklch(0.945 0.014 265); |
| 511 | --sidebar-border: var(--color-zinc-200); | 589 | --card-foreground: var(--foreground); |
| 590 | --accent: oklch(0.875 0.008 265); | ||
| 591 | --accent-foreground: oklch(0.24 0.012 265); | ||
| 592 | --muted: oklch(0.9 0.018 265); | ||
| 593 | --muted-foreground: oklch(0.49 0.03 265); | ||
| 594 | --border: oklch(0.72 0.025 265 / 28%); | ||
| 595 | --input: oklch(0.62 0.03 265 / 28%); | ||
| 596 | --sidebar: var(--card); | ||
| 597 | --sidebar-foreground: var(--foreground); | ||
| 598 | --sidebar-muted-foreground: var(--muted-foreground); | ||
| 599 | --sidebar-control-surface: var(--muted); | ||
| 600 | --sidebar-row-hover: color-mix(in srgb, var(--foreground) 6%, transparent); | ||
| 601 | --sidebar-row-active: color-mix(in srgb, var(--foreground) 11%, transparent); | ||
| 602 | --sidebar-row-selected: color-mix(in srgb, var(--foreground) 7%, transparent); | ||
| 603 | --sidebar-border: var(--border); | ||
| 512 | --sidebar-stage-fade: var(--sidebar); | 604 | --sidebar-stage-fade: var(--sidebar); |
| 513 | } | 605 | background-color: var(--card); |
| 514 | 606 | } | |
| 515 | .dark [data-app-sidebar] { | 607 | |
| 516 | --sidebar: var(--color-zinc-950); | 608 | .dark [data-sidebar-version="v1"], |
| 517 | --sidebar-foreground: var(--color-zinc-100); | 609 | .dark [data-sidebar-version="v2"] { |
| 518 | --sidebar-muted-foreground: var(--color-zinc-400); | 610 | --background: #000; |
| 519 | --sidebar-control-surface: var(--color-zinc-800); | 611 | --foreground: #f1f3f7; |
| 520 | --sidebar-row-hover: var(--color-zinc-900); | 612 | --card: #000; |
| 521 | --sidebar-row-active: var(--color-zinc-900); | 613 | --card-foreground: var(--foreground); |
| 522 | --sidebar-row-selected: var(--color-zinc-900); | 614 | --accent: #191a1d; |
| 523 | --sidebar-border: var(--color-zinc-800); | 615 | --accent-foreground: #f7f9ff; |
| 616 | --muted: #0a0a0a; | ||
| 617 | --muted-foreground: #a3a3a3; | ||
| 618 | --border: rgb(255 255 255 / 8%); | ||
| 619 | --input: rgb(255 255 255 / 18%); | ||
| 620 | --sidebar: var(--card); | ||
| 621 | --sidebar-foreground: var(--foreground); | ||
| 622 | --sidebar-muted-foreground: var(--muted-foreground); | ||
| 623 | --sidebar-control-surface: var(--muted); | ||
| 624 | --sidebar-row-hover: color-mix(in srgb, var(--foreground) 8%, transparent); | ||
| 625 | --sidebar-row-active: color-mix(in srgb, var(--foreground) 11%, transparent); | ||
| 626 | --sidebar-row-selected: color-mix(in srgb, var(--foreground) 7%, transparent); | ||
| 627 | --sidebar-border: var(--border); | ||
| 628 | /* The stage-channel header art must ramp to THIS panel's surface, not the | ||
| 629 | global chrome background, or the fade shows a seam (same rule as the | ||
| 630 | light palette above). */ | ||
| 631 | --sidebar-stage-fade: var(--card); | ||
| 524 | } | 632 | } |
| 525 | 633 | ||
| 526 | body { | 634 | body { |
apps/web/src/routes/settings.tsx+3-3| ... | @@ -22,7 +22,7 @@ function RestoreDefaultsButton({ onRestored }: { onRestored: () => void }) { | ... | @@ -22,7 +22,7 @@ function RestoreDefaultsButton({ onRestored }: { onRestored: () => void }) { |
| 22 | return ( | 22 | return ( |
| 23 | <Button | 23 | <Button |
| 24 | size="xs" | 24 | size="xs" |
| 25 | variant="outline" | 25 | variant="ghost" |
| 26 | disabled={changedSettingLabels.length === 0} | 26 | disabled={changedSettingLabels.length === 0} |
| 27 | onClick={() => void restoreDefaults()} | 27 | onClick={() => void restoreDefaults()} |
| 28 | > | 28 | > |
| ... | @@ -68,7 +68,7 @@ function SettingsContentLayout() { | ... | @@ -68,7 +68,7 @@ function SettingsContentLayout() { |
| 68 | {!isElectron && ( | 68 | {!isElectron && ( |
| 69 | <header | 69 | <header |
| 70 | className={cn( | 70 | className={cn( |
| 71 | "border-b border-border px-3 py-2 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | 71 | "px-3 py-2 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", |
| 72 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 72 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 73 | )} | 73 | )} |
| 74 | > | 74 | > |
| ... | @@ -86,7 +86,7 @@ function SettingsContentLayout() { | ... | @@ -86,7 +86,7 @@ function SettingsContentLayout() { |
| 86 | {isElectron && ( | 86 | {isElectron && ( |
| 87 | <div | 87 | <div |
| 88 | className={cn( | 88 | className={cn( |
| 89 | "drag-region flex h-[52px] shrink-0 items-center border-b border-border px-5 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]", | 89 | "drag-region flex h-[52px] shrink-0 items-center px-5 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]", |
| 90 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 90 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 91 | )} | 91 | )} |
| 92 | > | 92 | > |