From 9d2d3f044d6589a169f367f041cd2633aa0ef489 Mon Sep 17 00:00:00 2001 From: Chris Scott <99081550+chriswritescode-dev@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:56:52 -0500 Subject: [PATCH 01/13] Add OAuth authentication support for MCP servers --- .../src/components/repo/RepoMcpDialog.tsx | 156 +++++++-- .../settings/AddMcpServerDialog.tsx | 306 +++++++++++------- .../src/components/settings/McpManager.tsx | 103 ++++-- .../components/settings/McpOAuthDialog.tsx | 266 +++++++++++++++ .../src/components/settings/McpServerCard.tsx | 153 +++++---- 5 files changed, 779 insertions(+), 205 deletions(-) create mode 100644 frontend/src/components/settings/McpOAuthDialog.tsx diff --git a/frontend/src/components/repo/RepoMcpDialog.tsx b/frontend/src/components/repo/RepoMcpDialog.tsx index 5aacf365..a5b0d50a 100644 --- a/frontend/src/components/repo/RepoMcpDialog.tsx +++ b/frontend/src/components/repo/RepoMcpDialog.tsx @@ -1,21 +1,17 @@ import { useState, useEffect, useCallback } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog' import { Switch } from '@/components/ui/switch' +import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' -import { Loader2, XCircle, AlertCircle, Plug } from 'lucide-react' -import { mcpApi, type McpStatus } from '@/api/mcp' +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' +import { DeleteDialog } from '@/components/ui/delete-dialog' +import { DropdownMenuSeparator } from '@/components/ui/dropdown-menu' +import { Loader2, XCircle, AlertCircle, Plug, Shield, MoreVertical, Key, RefreshCw } from 'lucide-react' +import { McpOAuthDialog } from '@/components/settings/McpOAuthDialog' +import { mcpApi, type McpStatus, type McpServerConfig, type McpAuthStartResponse } from '@/api/mcp' import { useMutation } from '@tanstack/react-query' import { showToast } from '@/lib/toast' -interface McpServerConfig { - type: 'local' | 'remote' - enabled?: boolean - command?: string[] - url?: string - environment?: Record - timeout?: number -} - interface RepoMcpDialogProps { open: boolean onOpenChange: (open: boolean) => void @@ -28,6 +24,8 @@ interface RepoMcpDialogProps { export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcpDialogProps) { const [localStatus, setLocalStatus] = useState>({}) const [isLoadingStatus, setIsLoadingStatus] = useState(false) + const [removeAuthConfirmServer, setRemoveAuthConfirmServer] = useState(null) + const [authDialogServerId, setAuthDialogServerId] = useState(null) const mcpServers = config?.content?.mcp as Record | undefined || {} const serverIds = Object.keys(mcpServers) @@ -68,6 +66,40 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp showToast.error(error instanceof Error ? error.message : 'Failed to update MCP server') }, }) + + const removeAuthMutation = useMutation({ + mutationFn: async (serverId: string) => { + if (!directory) throw new Error('No directory provided') + await mcpApi.removeAuthDirectory(serverId, directory) + }, + onSuccess: async () => { + showToast.success('Authentication removed for this location') + setRemoveAuthConfirmServer(null) + await fetchStatus() + }, + onError: (error) => { + showToast.error(error instanceof Error ? error.message : 'Failed to remove authentication') + }, + }) + + const handleOAuthAutoAuth = async () => { + if (!authDialogServerId || !directory) return + await mcpApi.authenticateDirectory(authDialogServerId, directory) + await fetchStatus() + setAuthDialogServerId(null) + } + + const handleOAuthStartAuth = async (): Promise => { + if (!authDialogServerId) throw new Error('No server ID') + return await mcpApi.startAuth(authDialogServerId) + } + + const handleOAuthCompleteAuth = async (code: string) => { + if (!authDialogServerId) return + await mcpApi.completeAuth(authDialogServerId, code) + await fetchStatus() + setAuthDialogServerId(null) + } useEffect(() => { if (open && directory) { @@ -156,7 +188,14 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp const serverConfig = mcpServers[serverId] const status = localStatus[serverId] const isConnected = status?.status === 'connected' + const needsAuth = status?.status === 'needs_auth' const failed = status?.status === 'failed' + const isRemote = serverConfig.type === 'remote' + const hasOAuthConfig = isRemote && !!serverConfig.oauth + const hasOAuthError = failed && isRemote && /oauth|auth.*state/i.test(status.error) + const isOAuthServer = hasOAuthConfig || hasOAuthError || (needsAuth && isRemote) + const connectedWithOAuth = isOAuthServer && isConnected + const showAuthButton = needsAuth || (isOAuthServer && failed) return (
{getDisplayName(serverId)}

+ {connectedWithOAuth && ( + + + + )} {getStatusBadge(status)}

@@ -181,20 +225,94 @@ export function RepoMcpDialog({ open, onOpenChange, config, directory }: RepoMcp )} - { - toggleMutation.mutate({ serverId, enable: enabled }) - }} - onClick={(e) => e.stopPropagation()} - /> +

+ {showAuthButton ? ( + + ) : ( + { + toggleMutation.mutate({ serverId, enable: enabled }) + }} + onClick={(e) => e.stopPropagation()} + /> + )} + {(isOAuthServer || needsAuth) && ( + + + + + + {showAuthButton && ( + setAuthDialogServerId(serverId)}> + + Authenticate + + )} + {connectedWithOAuth && ( + setAuthDialogServerId(serverId)}> + + Re-authenticate + + )} + {connectedWithOAuth && ( + <> + + setRemoveAuthConfirmServer(serverId)} + disabled={removeAuthMutation.isPending} + > + + {removeAuthMutation.isPending ? 'Removing...' : 'Remove Auth'} + + + )} + + + )} +
) })} )} + + setRemoveAuthConfirmServer(null)} + onConfirm={() => { + if (removeAuthConfirmServer) { + removeAuthMutation.mutate(removeAuthConfirmServer) + } + }} + onCancel={() => setRemoveAuthConfirmServer(null)} + title="Remove Authentication" + description="This will remove the OAuth credentials for this MCP server at this location. You will need to re-authenticate to use this server here again." + itemName={removeAuthConfirmServer ? getDisplayName(removeAuthConfirmServer) : ''} + isDeleting={removeAuthMutation.isPending} + /> + + !o && setAuthDialogServerId(null)} + serverName={authDialogServerId || ''} + onAutoAuth={handleOAuthAutoAuth} + onStartAuth={handleOAuthStartAuth} + onCompleteAuth={handleOAuthCompleteAuth} + directory={directory} + /> ) diff --git a/frontend/src/components/settings/AddMcpServerDialog.tsx b/frontend/src/components/settings/AddMcpServerDialog.tsx index 08b961cf..5a0dcf00 100644 --- a/frontend/src/components/settings/AddMcpServerDialog.tsx +++ b/frontend/src/components/settings/AddMcpServerDialog.tsx @@ -30,6 +30,10 @@ export function AddMcpServerDialog({ open, onOpenChange, onUpdate }: AddMcpServe const [environment, setEnvironment] = useState([]) const [timeout, setTimeout] = useState('') const [enabled, setEnabled] = useState(true) + const [oauthEnabled, setOauthEnabled] = useState(false) + const [oauthClientId, setOauthClientId] = useState('') + const [oauthClientSecret, setOauthClientSecret] = useState('') + const [oauthScope, setOauthScope] = useState('') const queryClient = useQueryClient() const { addServerAsync, isAddingServer } = useMcpServers() @@ -67,6 +71,14 @@ export function AddMcpServerDialog({ open, onOpenChange, onUpdate }: AddMcpServe throw new Error('URL is required for remote MCP servers') } mcpConfig.url = url.trim() + + if (oauthEnabled) { + const oauthConfig: Record = {} + if (oauthClientId.trim()) oauthConfig.clientId = oauthClientId.trim() + if (oauthClientSecret.trim()) oauthConfig.clientSecret = oauthClientSecret.trim() + if (oauthScope.trim()) oauthConfig.scope = oauthScope.trim() + mcpConfig.oauth = Object.keys(oauthConfig).length > 0 ? oauthConfig : true + } } if (timeout && parseInt(timeout)) { @@ -84,6 +96,15 @@ export function AddMcpServerDialog({ open, onOpenChange, onUpdate }: AddMcpServe await settingsApi.updateOpenCodeConfig(config.name, { content: updatedConfig }) if (enabled) { + const buildOauthField = () => { + if (serverType !== 'remote' || !oauthEnabled) return undefined + const cfg: Record = {} + if (oauthClientId.trim()) cfg.clientId = oauthClientId.trim() + if (oauthClientSecret.trim()) cfg.clientSecret = oauthClientSecret.trim() + if (oauthScope.trim()) cfg.scope = oauthScope.trim() + return Object.keys(cfg).length > 0 ? cfg : true + } + await addServerAsync({ name: serverId, config: { @@ -100,6 +121,7 @@ export function AddMcpServerDialog({ open, onOpenChange, onUpdate }: AddMcpServe }, {} as Record) : undefined, timeout: timeout && parseInt(timeout) ? parseInt(timeout) : undefined, + oauth: buildOauthField(), } }) } @@ -146,6 +168,10 @@ export function AddMcpServerDialog({ open, onOpenChange, onUpdate }: AddMcpServe setEnvironment([]) setTimeout('') setEnabled(true) + setOauthEnabled(false) + setOauthClientId('') + setOauthClientSecret('') + setOauthScope('') onOpenChange(false) } @@ -153,149 +179,203 @@ export function AddMcpServerDialog({ open, onOpenChange, onUpdate }: AddMcpServe return ( - - + + Add MCP Server Configure a new Model Context Protocol server for your OpenCode configuration -
-
- - setServerId(e.target.value)} - placeholder="e.g., filesystem, git, my-server" - className="bg-background border-border" - /> -

- Unique identifier for this MCP server (lowercase, no spaces) -

-
- -
- - -
- - {serverType === 'local' ? ( +
+
- + setCommand(e.target.value)} - placeholder="npx @modelcontextprotocol/server-filesystem /tmp" - className="bg-background border-border font-mono" + id="serverId" + value={serverId} + onChange={(e) => setServerId(e.target.value)} + placeholder="e.g., filesystem, git, my-server" + className="bg-background border-border" />

- Command and arguments to run the MCP server + Unique identifier for this MCP server (lowercase, no spaces)

- ) : ( +
- - setUrl(e.target.value)} - placeholder="http://localhost:3000/mcp" - className="bg-background border-border font-mono" - /> -

- URL of the remote MCP server -

+ +
- )} - {serverType === 'local' && ( -
-
- - + {serverType === 'local' ? ( +
+ + setCommand(e.target.value)} + placeholder="npx @modelcontextprotocol/server-filesystem /tmp" + className="bg-background border-border font-mono" + /> +

+ Command and arguments to run the MCP server +

- {environment.map((env, index) => ( -
- handleUpdateEnvironmentVar(index, 'key', e.target.value)} - placeholder="API_KEY" - className="bg-background border-border font-mono" - /> - handleUpdateEnvironmentVar(index, 'value', e.target.value)} - placeholder="your-api-key-here" - className="bg-background border-border font-mono" + ) : ( +
+ + setUrl(e.target.value)} + placeholder="http://localhost:3000/mcp" + className="bg-background border-border font-mono" + /> +

+ URL of the remote MCP server +

+
+ )} + + {serverType === 'remote' && ( +
+
+ - {environment.length > 1 && ( - - )} +
- ))} + {oauthEnabled && ( +
+

+ Leave fields blank to use the server's default OAuth discovery +

+
+ + setOauthClientId(e.target.value)} + placeholder="Optional" + className="bg-background border-border font-mono" + /> +
+
+ + setOauthClientSecret(e.target.value)} + placeholder="Optional" + className="bg-background border-border font-mono" + /> +
+
+ + setOauthScope(e.target.value)} + placeholder="e.g., read write" + className="bg-background border-border font-mono" + /> +
+
+ )} +
+ )} + + {serverType === 'local' && ( +
+
+ + +
+ {environment.map((env, index) => ( +
+ handleUpdateEnvironmentVar(index, 'key', e.target.value)} + placeholder="API_KEY" + className="bg-background border-border font-mono" + /> + handleUpdateEnvironmentVar(index, 'value', e.target.value)} + placeholder="your-api-key-here" + className="bg-background border-border font-mono" + /> + {environment.length > 1 && ( + + )} +
+ ))} +

+ Environment variables to set when running the MCP server +

+
+ )} + +
+ + setTimeout(e.target.value)} + placeholder="5000" + className="bg-background border-border" + />

- Environment variables to set when running the MCP server + Timeout in milliseconds for fetching tools (default: 5000)

- )} -
- - setTimeout(e.target.value)} - placeholder="5000" - className="bg-background border-border" - /> -

- Timeout in milliseconds for fetching tools (default: 5000) -

-
- -
- - +
+ + +
- -
{Object.keys(mcpServers).length === 0 ? ( - - -

No MCP servers configured. Add your first server to get started.

-
-
+
+

No MCP servers configured. Add your first server to get started.

+
) : ( -
+
{Object.entries(mcpServers).map(([serverId, serverConfig]) => { const status = mcpStatus?.[serverId] const isConnected = status?.status === 'connected' @@ -186,7 +225,10 @@ export function McpManager({ config, onUpdate, onConfigUpdate }: McpManagerProps errorMessage={errorMessage} isAnyOperationPending={isAnyOperationPending} togglingServerId={togglingServerId} + isRemovingAuth={isRemovingAuth} onToggleServer={handleToggleServer} + onAuthenticate={handleAuthenticate} + onRemoveAuth={handleRemoveAuth} onDeleteServer={(id, name) => setDeleteConfirmServer({ id, name })} /> ) @@ -204,6 +246,31 @@ export function McpManager({ config, onUpdate, onConfigUpdate }: McpManagerProps itemName={deleteConfirmServer?.name} isDeleting={deleteServerMutation.isPending} /> + + !open && setAuthDialogServerId(null)} + serverName={authDialogServerId || ''} + onAutoAuth={handleOAuthAutoAuth} + onStartAuth={handleOAuthStartAuth} + onCompleteAuth={handleOAuthCompleteAuth} + /> + + setRemoveAuthConfirmServer(null)} + onConfirm={handleConfirmRemoveAuth} + onCancel={() => setRemoveAuthConfirmServer(null)} + title="Remove Authentication" + description="This will remove the OAuth credentials for this MCP server. You will need to re-authenticate to use this server again." + itemName={mcpServers[removeAuthConfirmServer || ''] ? getDisplayName(removeAuthConfirmServer || '') : ''} + isDeleting={isRemovingAuth} + />
) + + function getDisplayName(serverId: string): string { + const name = serverId.replace(/[-_]/g, ' ') + return name.charAt(0).toUpperCase() + name.slice(1) + } } diff --git a/frontend/src/components/settings/McpOAuthDialog.tsx b/frontend/src/components/settings/McpOAuthDialog.tsx new file mode 100644 index 00000000..25ff8a4e --- /dev/null +++ b/frontend/src/components/settings/McpOAuthDialog.tsx @@ -0,0 +1,266 @@ +import { useState } from 'react' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Loader2, ExternalLink, Key, XCircle } from 'lucide-react' +import type { McpAuthStartResponse } from '@/api/mcp' + +interface McpOAuthDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + serverName: string + onAutoAuth: () => Promise + onStartAuth: () => Promise + onCompleteAuth: (code: string) => Promise + directory?: string +} + +export function McpOAuthDialog({ + open, + onOpenChange, + serverName, + onAutoAuth, + onStartAuth, + onCompleteAuth, + directory +}: McpOAuthDialogProps) { + const [step, setStep] = useState<'method' | 'auto' | 'manual' | 'enter_code'>('method') + const [loading, setLoading] = useState(false) + const [authUrl, setAuthUrl] = useState(null) + const [authCode, setAuthCode] = useState('') + const [error, setError] = useState(null) + + const handleAutoAuth = async () => { + setLoading(true) + setError(null) + setStep('auto') + try { + await onAutoAuth() + onOpenChange(false) + resetState() + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to authenticate') + setStep('method') + } finally { + setLoading(false) + } + } + + const handleStartManualAuth = async () => { + setLoading(true) + setError(null) + try { + const result = await onStartAuth() + setAuthUrl(result.authorizationUrl) + setStep('manual') + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to start authentication') + } finally { + setLoading(false) + } + } + + const handleCompleteAuth = async () => { + if (!authCode.trim()) return + + setLoading(true) + setError(null) + try { + await onCompleteAuth(authCode.trim()) + onOpenChange(false) + resetState() + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to complete authentication') + } finally { + setLoading(false) + } + } + + const handleOpenAuthUrl = () => { + if (authUrl) { + window.open(authUrl, '_blank') + setStep('enter_code') + } + } + + const resetState = () => { + setStep('method') + setAuthUrl(null) + setAuthCode('') + setError(null) + setLoading(false) + } + + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen) { + resetState() + } + onOpenChange(newOpen) + } + + const scopes = directory ? 'this location' : 'global' + + return ( + + + + + + Connect {serverName} + + + Authenticate to use this MCP server for {scopes} + + + +
+ {error && ( + + + {error} + + )} + + {step === 'method' && ( +
+

Choose an authentication method:

+
+ + +
+
+ )} + + {step === 'auto' && ( +
+
+ +

+ Waiting for browser authorization... +

+
+
+ )} + + {step === 'manual' && authUrl && ( +
+

+ Open the authorization link in your browser to begin the flow. Then paste the code below. +

+ +
+ {authUrl} +
+
+ )} + + {step === 'enter_code' && ( +
+

+ Paste the authorization code from the browser: +

+
+ + setAuthCode(e.target.value)} + placeholder="Paste code here..." + disabled={loading} + /> +
+
+ )} +
+ + + {step === 'enter_code' && ( + <> + + + + )} + {(step === 'method' || step === 'manual') && ( + + )} + +
+
+ ) +} diff --git a/frontend/src/components/settings/McpServerCard.tsx b/frontend/src/components/settings/McpServerCard.tsx index eb87066d..044381e3 100644 --- a/frontend/src/components/settings/McpServerCard.tsx +++ b/frontend/src/components/settings/McpServerCard.tsx @@ -1,18 +1,10 @@ -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { Button } from '@/components/ui/button' -import { Trash2, XCircle, AlertCircle, Key } from 'lucide-react' -import type { McpStatus } from '@/api/mcp' - -interface McpServerConfig { - type: 'local' | 'remote' - enabled?: boolean - command?: string[] - url?: string - environment?: Record - timeout?: number -} +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' +import { DropdownMenuSeparator } from '@/components/ui/dropdown-menu' +import { XCircle, AlertCircle, Key, MoreVertical, Shield, Trash2, RefreshCw } from 'lucide-react' +import type { McpStatus, McpServerConfig } from '@/api/mcp' interface McpServerCardProps { serverId: string @@ -22,7 +14,10 @@ interface McpServerCardProps { errorMessage: string | null isAnyOperationPending: boolean togglingServerId: string | null + isRemovingAuth: boolean onToggleServer: (serverId: string) => void + onAuthenticate?: (serverId: string) => void + onRemoveAuth?: (serverId: string) => void onDeleteServer: (serverId: string, serverName: string) => void } @@ -91,55 +86,103 @@ export function McpServerCard({ errorMessage, isAnyOperationPending, togglingServerId, + isRemovingAuth, onToggleServer, + onAuthenticate, + onRemoveAuth, onDeleteServer }: McpServerCardProps) { + const needsAuth = status?.status === 'needs_auth' + const isRemote = serverConfig.type === 'remote' + const hasOAuthConfig = isRemote && !!serverConfig.oauth + const hasOAuthError = status?.status === 'failed' && isRemote && /oauth|auth.*state/i.test(status.error) + const isOAuthServer = hasOAuthConfig || hasOAuthError || (needsAuth && isRemote) + const connectedWithOAuth = isOAuthServer && isConnected + const showAuthButton = needsAuth || (isOAuthServer && status?.status === 'failed') + const displayName = getServerDisplayName(serverId) + return ( - - -
-
- {getServerDisplayName(serverId)} -
- {status ? getStatusBadge(status) : ( - Loading... - )} -
-
-
- onToggleServer(serverId)} - disabled={isAnyOperationPending || togglingServerId === serverId} - /> - -
-
-
- -
-

{getServerDescription(serverConfig)}

- {serverConfig.timeout && ( -

Timeout: {serverConfig.timeout}ms

- )} - {serverConfig.environment && Object.keys(serverConfig.environment).length > 0 && ( -

Environment variables: {Object.keys(serverConfig.environment).length} configured

+
+
+
+

{displayName}

+ {connectedWithOAuth && ( + + + )} - {errorMessage && ( -
- - {errorMessage} -
+ {status ? getStatusBadge(status) : ( + Loading... )}
- - +

+ {getServerDescription(serverConfig)} +

+ {errorMessage && ( +
+ + {errorMessage} +
+ )} +
+ +
+ {showAuthButton && onAuthenticate ? ( + + ) : ( + onToggleServer(serverId)} + disabled={isAnyOperationPending || togglingServerId === serverId} + /> + )} + + + + + + {showAuthButton && onAuthenticate && ( + onAuthenticate(serverId)}> + + Authenticate + + )} + {connectedWithOAuth && onAuthenticate && ( + onAuthenticate(serverId)}> + + Re-authenticate + + )} + {connectedWithOAuth && onRemoveAuth && ( + onRemoveAuth(serverId)} + disabled={isRemovingAuth} + > + + {isRemovingAuth ? 'Removing...' : 'Remove Auth'} + + )} + {(showAuthButton || connectedWithOAuth) && } + onDeleteServer(serverId, displayName)} + className="text-red-600" + > + + Delete Server + + + +
+
) } From d6726286fd37a8511f95eab5d2a1f4d56dd7b83a Mon Sep 17 00:00:00 2001 From: Chris Scott <99081550+chriswritescode-dev@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:56:55 -0500 Subject: [PATCH 02/13] Improve mobile text sizing with responsive text classes --- .../src/components/message/EditableUserMessage.tsx | 2 +- frontend/src/components/model/ModelSelectDialog.tsx | 2 +- frontend/src/components/session/QuestionPrompt.tsx | 2 +- frontend/src/components/settings/AccountSettings.tsx | 10 +++++----- frontend/src/components/settings/AgentDialog.tsx | 2 +- frontend/src/components/settings/AgentsMdEditor.tsx | 2 +- frontend/src/components/settings/CommandDialog.tsx | 2 +- .../src/components/settings/CreateConfigDialog.tsx | 2 +- frontend/src/components/settings/KeyboardShortcuts.tsx | 4 ++-- .../src/components/settings/OpenCodeConfigEditor.tsx | 2 +- frontend/src/components/settings/ProviderSettings.tsx | 2 +- frontend/src/components/settings/STTSettings.tsx | 4 ++-- frontend/src/components/source-control/BranchesTab.tsx | 2 +- frontend/src/components/source-control/ChangesTab.tsx | 4 ++-- frontend/src/components/ui/combobox.tsx | 2 +- frontend/src/components/ui/virtualized-text-view.tsx | 2 +- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/message/EditableUserMessage.tsx b/frontend/src/components/message/EditableUserMessage.tsx index 43de5ec3..cc3aa8ce 100644 --- a/frontend/src/components/message/EditableUserMessage.tsx +++ b/frontend/src/components/message/EditableUserMessage.tsx @@ -89,7 +89,7 @@ export const EditableUserMessage = memo(function EditableUserMessage({ onKeyDown={handleKeyDown} onFocus={() => setIsEditingMessage(true)} onBlur={() => setIsEditingMessage(false)} - className="w-full p-3 rounded-lg bg-background border border-primary/50 focus:border-primary focus:ring-1 focus:ring-primary outline-none resize-none min-h-[60px] text-sm" + className="w-full p-3 rounded-lg bg-background border border-primary/50 focus:border-primary focus:ring-1 focus:ring-primary outline-none resize-none min-h-[60px] text-[16px] md:text-sm" placeholder="Edit your message..." disabled={refreshMessage.isPending} /> diff --git a/frontend/src/components/model/ModelSelectDialog.tsx b/frontend/src/components/model/ModelSelectDialog.tsx index 6f0aa103..decc1797 100644 --- a/frontend/src/components/model/ModelSelectDialog.tsx +++ b/frontend/src/components/model/ModelSelectDialog.tsx @@ -57,7 +57,7 @@ function SearchInput({ onSearch, initialValue = "" }: SearchInputProps) { placeholder="Search models..." value={value} onChange={(e) => setValue(e.target.value)} - className="pl-10 text-sm" + className="pl-10 md:text-sm" />
diff --git a/frontend/src/components/session/QuestionPrompt.tsx b/frontend/src/components/session/QuestionPrompt.tsx index da675d07..b531fdd7 100644 --- a/frontend/src/components/session/QuestionPrompt.tsx +++ b/frontend/src/components/session/QuestionPrompt.tsx @@ -435,7 +435,7 @@ function QuestionStep({ value={customInput} onChange={(e) => onCustomInputChange(e.target.value)} placeholder="Type your own answer..." - className="min-h-[60px] sm:min-h-[80px] text-xs sm:text-sm resize-none border-blue-500/30 focus:border-blue-500" + className="min-h-[60px] sm:min-h-[80px] text-[16px] sm:text-xs md:text-sm resize-none border-blue-500/30 focus:border-blue-500" onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() diff --git a/frontend/src/components/settings/AccountSettings.tsx b/frontend/src/components/settings/AccountSettings.tsx index d355044f..932bf191 100644 --- a/frontend/src/components/settings/AccountSettings.tsx +++ b/frontend/src/components/settings/AccountSettings.tsx @@ -163,11 +163,11 @@ export function AccountSettings() {
- +
- +
@@ -227,7 +227,7 @@ export function AccountSettings() { value={newPassword} onChange={(e) => setNewPassword(e.target.value)} placeholder="At least 8 characters" - className="h-9 sm:h-10 text-sm" + className="h-9 sm:h-10 md:text-sm" />
@@ -271,7 +271,7 @@ export function AccountSettings() { placeholder="Passkey name (optional)" value={passkeyName} onChange={(e) => setPasskeyName(e.target.value)} - className="h-9 sm:h-10 text-sm" + className="h-9 sm:h-10 md:text-sm" />