فهرست منبع

UI improvements

paviko 4 ماه پیش
والد
کامیت
86f74f5977

+ 5 - 0
hosts/jetbrains-plugin/changelog.html

@@ -1,5 +1,10 @@
 <h2>OpenCode (unofficial) JetBrains Plugin Changelog</h2>
 <h2>OpenCode (unofficial) JetBrains Plugin Changelog</h2>
 
 
+<h3>2025.11.xx</h3>
+<ul>
+  <li>UI improvements</li>
+</ul>
+
 <h3>2025.11.20</h3>
 <h3>2025.11.20</h3>
 <ul>
 <ul>
   <li>Providers can be configured from Settings panel - can be added/removed, also OAuth</li>
   <li>Providers can be configured from Settings panel - can be added/removed, also OAuth</li>

+ 4 - 0
hosts/vscode-plugin/CHANGELOG.md

@@ -5,6 +5,10 @@ All notable changes to the OpenCode VSCode extension will be documented in this
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 
+## [25.11.xx] - 2025-11-xx
+
+- UI improvements
+
 ## [25.11.20] - 2025-11-20
 ## [25.11.20] - 2025-11-20
 
 
 - Providers can be configured from Settings panel - can be added/removed, also OAuth
 - Providers can be configured from Settings panel - can be added/removed, also OAuth

+ 57 - 60
packages/opencode/webgui/src/components/CompactHeader.tsx

@@ -249,14 +249,13 @@ const CompactHeader = forwardRef<
 
 
   return (
   return (
     <>
     <>
-      <header className="h-10 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 flex items-center justify-between px-3 flex-shrink-0 relative">
+      <header className="h-9 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 flex items-center justify-between px-2 flex-shrink-0 relative">
         {/* Left: Session dropdown */}
         {/* Left: Session dropdown */}
-        <div className="flex items-center gap-2" ref={dropdownRef}>
+        <div className="flex items-center gap-1.5" ref={dropdownRef}>
           <button
           <button
             onClick={() => setIsDropdownOpen(!isDropdownOpen)}
             onClick={() => setIsDropdownOpen(!isDropdownOpen)}
-            className={`flex items-center gap-1 text-sm hover:text-gray-900 dark:hover:text-gray-100 ${
-              currentHasDefaultTitle ? "text-gray-500 dark:text-gray-500 italic" : "text-gray-700 dark:text-gray-300"
-            }`}
+            className={`flex items-center gap-1 text-sm hover:text-gray-900 dark:hover:text-gray-100 ${currentHasDefaultTitle ? "text-gray-500 dark:text-gray-500 italic" : "text-gray-700 dark:text-gray-300"
+              }`}
             title={currentTitle}
             title={currentTitle}
           >
           >
             <span>{currentSession ? truncatedTitle : "No Session"}</span>
             <span>{currentSession ? truncatedTitle : "No Session"}</span>
@@ -266,7 +265,7 @@ const CompactHeader = forwardRef<
           </button>
           </button>
 
 
           {/* Usage summary in header */}
           {/* Usage summary in header */}
-          <div className="relative flex items-center gap-2 text-xs text-gray-700 dark:text-gray-300 select-none min-w-0">
+          <div className="relative flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 select-none min-w-0">
             {(() => {
             {(() => {
               const pct = Math.min(100, Math.max(0, usage.percentage))
               const pct = Math.min(100, Math.max(0, usage.percentage))
               const color =
               const color =
@@ -274,10 +273,10 @@ const CompactHeader = forwardRef<
               return (
               return (
                 <button
                 <button
                   onClick={() => setShowUsageDetails((v) => !v)}
                   onClick={() => setShowUsageDetails((v) => !v)}
-                  className="flex items-center gap-2 group whitespace-nowrap overflow-hidden"
+                  className="flex items-center gap-1.5 group whitespace-nowrap overflow-hidden"
                   title="Show usage details"
                   title="Show usage details"
                 >
                 >
-                  <div className="w-[100px] h-3 bg-gray-100 dark:bg-gray-800 rounded overflow-hidden relative">
+                  <div className="w-[80px] h-2.5 bg-gray-100 dark:bg-gray-800 rounded overflow-hidden relative">
                     <div className={`${color} h-3`} style={{ width: `${pct}%` }} />
                     <div className={`${color} h-3`} style={{ width: `${pct}%` }} />
                     <span className="absolute inset-0 flex items-center justify-center text-[10px] text-gray-900 dark:text-white drop-shadow-sm">
                     <span className="absolute inset-0 flex items-center justify-center text-[10px] text-gray-900 dark:text-white drop-shadow-sm">
                       {Math.round(pct)}%
                       {Math.round(pct)}%
@@ -293,32 +292,34 @@ const CompactHeader = forwardRef<
             })()}
             })()}
 
 
             {showUsageDetails && (
             {showUsageDetails && (
-              <div className="absolute top-full left-0 mt-1 p-2 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded shadow-lg z-50 min-w-[240px]">
-                <div className="flex items-center justify-between py-0.5">
-                  <span>Context used</span>
-                  <span className="tabular-nums">
-                    {formatK(usage.contextUsed)}/{formatK(usage.contextLimit)}
-                  </span>
-                </div>
-                <div className="flex items-center justify-between py-0.5">
-                  <span>Input tokens</span>
-                  <span className="tabular-nums">{formatK(usage.breakdown.input)}</span>
-                </div>
-                <div className="flex items-center justify-between py-0.5">
-                  <span>Cache write</span>
-                  <span className="tabular-nums">{formatK(usage.breakdown.cacheWrite)}</span>
-                </div>
-                <div className="flex items-center justify-between py-0.5">
-                  <span>Cache read</span>
-                  <span className="tabular-nums">{formatK(usage.breakdown.cacheRead)}</span>
-                </div>
-                <div className="flex items-center justify-between py-0.5">
-                  <span>Output tokens</span>
-                  <span className="tabular-nums">{formatK(usage.breakdown.output)}</span>
-                </div>
-                <div className="flex items-center justify-between py-0.5">
-                  <span>Reasoning tokens</span>
-                  <span className="tabular-nums">{formatK(usage.breakdown.reasoning)}</span>
+              <div className="modern-card absolute top-full left-0 mt-1 w-64 z-50 overflow-hidden ring-1 ring-black/5 p-2">
+                <div className="max-h-[calc(100vh-200px)] overflow-y-auto py-1">
+                  <div className="flex items-center justify-between py-0.5">
+                    <span>Context used</span>
+                    <span className="tabular-nums">
+                      {formatK(usage.contextUsed)}/{formatK(usage.contextLimit)}
+                    </span>
+                  </div>
+                  <div className="flex items-center justify-between py-0.5">
+                    <span>Input tokens</span>
+                    <span className="tabular-nums">{formatK(usage.breakdown.input)}</span>
+                  </div>
+                  <div className="flex items-center justify-between py-0.5">
+                    <span>Cache write</span>
+                    <span className="tabular-nums">{formatK(usage.breakdown.cacheWrite)}</span>
+                  </div>
+                  <div className="flex items-center justify-between py-0.5">
+                    <span>Cache read</span>
+                    <span className="tabular-nums">{formatK(usage.breakdown.cacheRead)}</span>
+                  </div>
+                  <div className="flex items-center justify-between py-0.5">
+                    <span>Output tokens</span>
+                    <span className="tabular-nums">{formatK(usage.breakdown.output)}</span>
+                  </div>
+                  <div className="flex items-center justify-between py-0.5">
+                    <span>Reasoning tokens</span>
+                    <span className="tabular-nums">{formatK(usage.breakdown.reasoning)}</span>
+                  </div>
                 </div>
                 </div>
               </div>
               </div>
             )}
             )}
@@ -347,11 +348,10 @@ const CompactHeader = forwardRef<
                   />
                   />
                   <button
                   <button
                     onClick={() => setIsSelectMode(!isSelectMode)}
                     onClick={() => setIsSelectMode(!isSelectMode)}
-                    className={`px-2 h-[30px] flex items-center justify-center rounded border ${
-                      isSelectMode
-                        ? "bg-blue-500 text-white border-blue-500"
-                        : "bg-gray-100 text-gray-700 border-gray-300 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600"
-                    }`}
+                    className={`px-2 h-[30px] flex items-center justify-center rounded border ${isSelectMode
+                      ? "bg-blue-500 text-white border-blue-500"
+                      : "bg-gray-100 text-gray-700 border-gray-300 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600"
+                      }`}
                     title={isSelectMode ? "Cancel selection" : "Select multiple sessions"}
                     title={isSelectMode ? "Cancel selection" : "Select multiple sessions"}
                   >
                   >
                     {isSelectMode ? (
                     {isSelectMode ? (
@@ -390,13 +390,12 @@ const CompactHeader = forwardRef<
                         key={session.id}
                         key={session.id}
                         ref={index === selectedSessionIndex ? selectedSessionRef : null}
                         ref={index === selectedSessionIndex ? selectedSessionRef : null}
                         tabIndex={-1}
                         tabIndex={-1}
-                        className={`group px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 flex items-center justify-between outline-none ${
-                          index === selectedSessionIndex && !isSelectMode
+                        className={`group px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 flex items-center justify-between outline-none ${index === selectedSessionIndex && !isSelectMode
+                          ? "bg-blue-50 dark:bg-blue-950"
+                          : isActive
                             ? "bg-blue-50 dark:bg-blue-950"
                             ? "bg-blue-50 dark:bg-blue-950"
-                            : isActive
-                              ? "bg-blue-50 dark:bg-blue-950"
-                              : ""
-                        }`}
+                            : ""
+                          }`}
                         onClick={() => !isEditing && !isSelectMode && handleSessionSelect(session.id)}
                         onClick={() => !isEditing && !isSelectMode && handleSessionSelect(session.id)}
                         onKeyDown={handleKeyDown}
                         onKeyDown={handleKeyDown}
                       >
                       >
@@ -445,13 +444,12 @@ const CompactHeader = forwardRef<
                                 </svg>
                                 </svg>
                               )}
                               )}
                               <span
                               <span
-                                className={`truncate ${
-                                  hasDefaultTitle
-                                    ? "text-gray-500 dark:text-gray-500 italic"
-                                    : isActive && !isSelectMode
-                                      ? "text-blue-900 dark:text-blue-100 font-medium"
-                                      : "text-gray-700 dark:text-gray-300"
-                                }`}
+                                className={`truncate ${hasDefaultTitle
+                                  ? "text-gray-500 dark:text-gray-500 italic"
+                                  : isActive && !isSelectMode
+                                    ? "text-blue-900 dark:text-blue-100 font-medium"
+                                    : "text-gray-700 dark:text-gray-300"
+                                  }`}
                               >
                               >
                                 {displayTitle}
                                 {displayTitle}
                               </span>
                               </span>
@@ -524,19 +522,18 @@ const CompactHeader = forwardRef<
         </div>
         </div>
 
 
         {/* Right: Connection status, theme toggle, and new session button */}
         {/* Right: Connection status, theme toggle, and new session button */}
-        <div className="flex items-center gap-2">
+        <div className="flex items-center gap-1">
           {/* Connection status dot */}
           {/* Connection status dot */}
           <div
           <div
-            className={`w-2 h-2 rounded-full ${CONNECTION_COLORS[connectionState]} ${
-              connectionState === "connecting" || connectionState === "error" ? "animate-pulse" : ""
-            }`}
+            className={`w-2 h-2 rounded-full ${CONNECTION_COLORS[connectionState]} ${connectionState === "connecting" || connectionState === "error" ? "animate-pulse" : ""
+              }`}
             title={CONNECTION_TOOLTIPS[connectionState]}
             title={CONNECTION_TOOLTIPS[connectionState]}
           />
           />
 
 
           {/* Theme toggle */}
           {/* Theme toggle */}
           <button
           <button
             onClick={toggleTheme}
             onClick={toggleTheme}
-            className="w-6 h-6 flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
+            className="modern-icon-button w-7 h-7 flex items-center justify-center"
             title={theme === "light" ? "Switch to dark mode" : "Switch to light mode"}
             title={theme === "light" ? "Switch to dark mode" : "Switch to light mode"}
           >
           >
             {theme === "light" ? (
             {theme === "light" ? (
@@ -563,7 +560,7 @@ const CompactHeader = forwardRef<
           {/* Command Palette button */}
           {/* Command Palette button */}
           <button
           <button
             onClick={onOpenCommandPalette}
             onClick={onOpenCommandPalette}
-            className="w-6 h-6 flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
+            className="modern-icon-button w-7 h-7 flex items-center justify-center"
             title="Command Palette (Cmd/Ctrl+K)"
             title="Command Palette (Cmd/Ctrl+K)"
           >
           >
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -579,7 +576,7 @@ const CompactHeader = forwardRef<
           {/* Settings button */}
           {/* Settings button */}
           <button
           <button
             onClick={() => setIsSettingsOpen(true)}
             onClick={() => setIsSettingsOpen(true)}
-            className="w-6 h-6 flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
+            className="modern-icon-button w-7 h-7 flex items-center justify-center"
             title="Settings (Cmd/Ctrl+,)"
             title="Settings (Cmd/Ctrl+,)"
           >
           >
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -597,7 +594,7 @@ const CompactHeader = forwardRef<
           <button
           <button
             onClick={onNewSession}
             onClick={onNewSession}
             disabled={isCreatingSession}
             disabled={isCreatingSession}
-            className="w-6 h-6 flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
+            className="w-5 h-5 flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
             title="New Session (Cmd/Ctrl+N)"
             title="New Session (Cmd/Ctrl+N)"
           >
           >
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">

+ 9 - 9
packages/opencode/webgui/src/components/ConfirmModal.tsx

@@ -41,15 +41,15 @@ export function ConfirmModal({
 
 
   const variantStyles = {
   const variantStyles = {
     danger: {
     danger: {
-      button: "bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800",
+      button: "modern-button-danger",
       text: "text-red-900 dark:text-red-100",
       text: "text-red-900 dark:text-red-100",
     },
     },
     warning: {
     warning: {
-      button: "bg-yellow-600 hover:bg-yellow-700 dark:bg-yellow-700 dark:hover:bg-yellow-800",
-      text: "text-yellow-900 dark:text-yellow-100",
+      button: "bg-amber-500 hover:bg-amber-600 text-white border-transparent",
+      text: "text-amber-900 dark:text-amber-100",
     },
     },
     info: {
     info: {
-      button: "bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800",
+      button: "modern-button-primary",
       text: "text-blue-900 dark:text-blue-100",
       text: "text-blue-900 dark:text-blue-100",
     },
     },
   }
   }
@@ -57,9 +57,9 @@ export function ConfirmModal({
   const styles = variantStyles[variant]
   const styles = variantStyles[variant]
 
 
   return (
   return (
-    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" onClick={onClose}>
+    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm transition-all" onClick={onClose}>
       <div
       <div
-        className="bg-white dark:bg-gray-900 rounded-lg shadow-lg max-w-md w-full mx-4 border border-gray-200 dark:border-gray-800"
+        className="modern-card max-w-md w-full mx-4 overflow-hidden transform transition-all scale-100"
         onClick={(e) => e.stopPropagation()}
         onClick={(e) => e.stopPropagation()}
       >
       >
         {/* Header */}
         {/* Header */}
@@ -73,18 +73,18 @@ export function ConfirmModal({
         </div>
         </div>
 
 
         {/* Footer */}
         {/* Footer */}
-        <div className="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-b-lg flex justify-end gap-2">
+        <div className="px-4 py-3 bg-gray-50 dark:bg-gray-950/50 border-t border-gray-200 dark:border-gray-800 flex justify-end gap-2">
           <button
           <button
             onClick={onClose}
             onClick={onClose}
             disabled={isLoading}
             disabled={isLoading}
-            className="px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
+            className="modern-button modern-button-secondary"
           >
           >
             {cancelText}
             {cancelText}
           </button>
           </button>
           <button
           <button
             onClick={onConfirm}
             onClick={onConfirm}
             disabled={isLoading}
             disabled={isLoading}
-            className={`px-3 py-1.5 text-sm font-medium text-white ${styles.button} rounded disabled:opacity-50 disabled:cursor-not-allowed`}
+            className={`modern-button ${styles.button}`}
           >
           >
             {isLoading ? "Processing..." : confirmText}
             {isLoading ? "Processing..." : confirmText}
           </button>
           </button>

+ 9 - 9
packages/opencode/webgui/src/components/KeyboardShortcutsHelp.tsx

@@ -19,46 +19,46 @@ export function KeyboardShortcutsHelp({ isOpen, onClose }: KeyboardShortcutsHelp
       aria-labelledby="shortcuts-help-title"
       aria-labelledby="shortcuts-help-title"
     >
     >
       <div
       <div
-        className="max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-lg border border-gray-300 bg-white p-6 shadow-xl dark:border-gray-700 dark:bg-gray-900"
+        className="modern-card max-h-[80vh] w-full max-w-2xl overflow-y-auto p-4 shadow-xl"
         onClick={(e) => e.stopPropagation()}
         onClick={(e) => e.stopPropagation()}
       >
       >
         {/* Header */}
         {/* Header */}
-        <div className="mb-4 flex items-center justify-between">
+        <div className="mb-2 flex items-center justify-between">
           <h2 id="shortcuts-help-title" className="text-xl font-semibold text-gray-900 dark:text-gray-100">
           <h2 id="shortcuts-help-title" className="text-xl font-semibold text-gray-900 dark:text-gray-100">
             Keyboard Shortcuts
             Keyboard Shortcuts
           </h2>
           </h2>
           <button
           <button
             onClick={onClose}
             onClick={onClose}
-            className="rounded-md p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
+            className="modern-icon-button"
             aria-label="Close"
             aria-label="Close"
           >
           >
-            <svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
             </svg>
             </svg>
           </button>
           </button>
         </div>
         </div>
 
 
         {/* Shortcuts by category */}
         {/* Shortcuts by category */}
-        <div className="space-y-6">
+        <div className="space-y-4">
           {categories.map((category) => {
           {categories.map((category) => {
             const categoryShortcuts = KEYBOARD_SHORTCUTS.filter((s) => s.category === category)
             const categoryShortcuts = KEYBOARD_SHORTCUTS.filter((s) => s.category === category)
             return (
             return (
               <div key={category}>
               <div key={category}>
-                <h3 className="mb-3 text-sm font-semibold uppercase tracking-wide text-gray-600 dark:text-gray-400">
+                <h3 className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
                   {category}
                   {category}
                 </h3>
                 </h3>
                 <div className="space-y-2">
                 <div className="space-y-2">
                   {categoryShortcuts.map((shortcut, idx) => (
                   {categoryShortcuts.map((shortcut, idx) => (
                     <div
                     <div
                       key={idx}
                       key={idx}
-                      className="flex items-center justify-between rounded-md border border-gray-200 bg-gray-50 px-4 py-3 dark:border-gray-700 dark:bg-gray-800"
+                      className="flex items-center justify-between rounded border border-gray-200 bg-gray-50 px-3 py-1.5 dark:border-gray-800 dark:bg-gray-800/50"
                     >
                     >
                       <span className="text-sm text-gray-900 dark:text-gray-100">{shortcut.description}</span>
                       <span className="text-sm text-gray-900 dark:text-gray-100">{shortcut.description}</span>
                       <div className="flex gap-1">
                       <div className="flex gap-1">
                         {shortcut.keys.map((key, keyIdx) => (
                         {shortcut.keys.map((key, keyIdx) => (
                           <kbd
                           <kbd
                             key={keyIdx}
                             key={keyIdx}
-                            className="rounded border border-gray-300 bg-white px-2 py-1 text-xs font-semibold text-gray-700 shadow-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
+                            className="rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px] font-semibold text-gray-700 shadow-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
                           >
                           >
                             {key}
                             {key}
                           </kbd>
                           </kbd>
@@ -73,7 +73,7 @@ export function KeyboardShortcutsHelp({ isOpen, onClose }: KeyboardShortcutsHelp
         </div>
         </div>
 
 
         {/* Footer note */}
         {/* Footer note */}
-        <div className="mt-6 rounded-md border border-blue-200 bg-blue-50 p-3 dark:border-blue-900 dark:bg-blue-950">
+        <div className="mt-4 rounded border border-blue-200 bg-blue-50 p-2 dark:border-blue-900 dark:bg-blue-950/30">
           <p className="text-sm text-blue-800 dark:text-blue-200">
           <p className="text-sm text-blue-800 dark:text-blue-200">
             <strong>Tip:</strong> Use{" "}
             <strong>Tip:</strong> Use{" "}
             <kbd className="rounded border border-blue-300 bg-white px-1.5 py-0.5 text-xs font-semibold dark:border-blue-700 dark:bg-blue-900">
             <kbd className="rounded border border-blue-300 bg-white px-1.5 py-0.5 text-xs font-semibold dark:border-blue-700 dark:bg-blue-900">

+ 11 - 11
packages/opencode/webgui/src/components/MarkdownRenderer.tsx

@@ -99,16 +99,16 @@ type MarkdownCodeProps = ComponentPropsWithoutRef<"code"> &
 
 
 const markdownComponents: Partial<Components> = {
 const markdownComponents: Partial<Components> = {
   // Headings with proper hierarchy
   // Headings with proper hierarchy
-  h1: ({ children }) => <h1 className={`text-2xl font-bold mb-3 mt-4 ${styles.text}`}>{children}</h1>,
-  h2: ({ children }) => <h2 className={`text-xl font-bold mb-2 mt-3 ${styles.text}`}>{children}</h2>,
-  h3: ({ children }) => <h3 className={`text-lg font-bold mb-2 mt-3 ${styles.text}`}>{children}</h3>,
-  h4: ({ children }) => <h4 className={`text-base font-bold mb-2 mt-2 ${styles.text}`}>{children}</h4>,
-  h5: ({ children }) => <h5 className={`text-sm font-bold mb-1 mt-2 ${styles.text}`}>{children}</h5>,
-  h6: ({ children }) => <h6 className={`text-xs font-bold mb-1 mt-2 ${styles.text}`}>{children}</h6>,
+  h1: ({ children }) => <h1 className={`text-2xl font-bold mb-2 mt-3 ${styles.text}`}>{children}</h1>,
+  h2: ({ children }) => <h2 className={`text-xl font-bold mb-1.5 mt-2.5 ${styles.text}`}>{children}</h2>,
+  h3: ({ children }) => <h3 className={`text-lg font-bold mb-1.5 mt-2 ${styles.text}`}>{children}</h3>,
+  h4: ({ children }) => <h4 className={`text-base font-bold mb-1 mt-1.5 ${styles.text}`}>{children}</h4>,
+  h5: ({ children }) => <h5 className={`text-sm font-bold mb-1 mt-1.5 ${styles.text}`}>{children}</h5>,
+  h6: ({ children }) => <h6 className={`text-xs font-bold mb-1 mt-1.5 ${styles.text}`}>{children}</h6>,
 
 
   // Lists with proper indentation
   // Lists with proper indentation
-  ul: ({ children }) => <ul className={`list-disc list-inside mb-2 space-y-1 ${styles.text}`}>{children}</ul>,
-  ol: ({ children }) => <ol className={`list-decimal list-inside mb-2 space-y-1 ${styles.text}`}>{children}</ol>,
+  ul: ({ children }) => <ul className={`list-disc list-inside mb-1.5 space-y-0.5 ${styles.text}`}>{children}</ul>,
+  ol: ({ children }) => <ol className={`list-decimal list-inside mb-1.5 space-y-0.5 ${styles.text}`}>{children}</ol>,
   li: ({ children }) => <li className={`ml-4 ${styles.text}`}>{children}</li>,
   li: ({ children }) => <li className={`ml-4 ${styles.text}`}>{children}</li>,
 
 
   // Blockquotes with left border
   // Blockquotes with left border
@@ -140,13 +140,13 @@ const markdownComponents: Partial<Components> = {
   p: (props) => {
   p: (props) => {
     const nodeHasBlock = remarkNodeHasBlockChild(props.node)
     const nodeHasBlock = remarkNodeHasBlockChild(props.node)
     if (nodeHasBlock) {
     if (nodeHasBlock) {
-      return <div className={`mb-2 ${styles.text} leading-relaxed`}>{props.children}</div>
+      return <div className={`mb-1.5 ${styles.text} leading-relaxed`}>{props.children}</div>
     }
     }
     const childHasBlock = hasBlockChild(props.children)
     const childHasBlock = hasBlockChild(props.children)
     if (childHasBlock) {
     if (childHasBlock) {
-      return <div className={`mb-2 ${styles.text} leading-relaxed`}>{props.children}</div>
+      return <div className={`mb-1.5 ${styles.text} leading-relaxed`}>{props.children}</div>
     }
     }
-    return <p className={`mb-2 ${styles.text} leading-relaxed`}>{props.children}</p>
+    return <p className={`mb-1.5 ${styles.text} leading-relaxed`}>{props.children}</p>
   },
   },
 
 
   // Links that open in new tab
   // Links that open in new tab

+ 38 - 38
packages/opencode/webgui/src/components/MessageInput.tsx

@@ -89,16 +89,16 @@ function insertPlainWithMentionsImpl(
       const md: any = isDir
       const md: any = isDir
         ? { type: "directory" as const, display: token, path: token.endsWith("/") ? token : token + "/" }
         ? { type: "directory" as const, display: token, path: token.endsWith("/") ? token : token + "/" }
         : (() => {
         : (() => {
-            const relBase = parsed.path
-            const display = parsed.range ? `${relBase}:${parsed.range.start}-${parsed.range.end}` : relBase
-            const base: any = { type: "file" as const, display, path: relBase }
-            if (parsed.range)
-              base.range = {
-                start: { line: parsed.range.start, character: 0 },
-                end: { line: parsed.range.end, character: 0 },
-              }
-            return base
-          })()
+          const relBase = parsed.path
+          const display = parsed.range ? `${relBase}:${parsed.range.start}-${parsed.range.end}` : relBase
+          const base: any = { type: "file" as const, display, path: relBase }
+          if (parsed.range)
+            base.range = {
+              start: { line: parsed.range.start, character: 0 },
+              end: { line: parsed.range.end, character: 0 },
+            }
+          return base
+        })()
       nodes.push($createMentionNode(md))
       nodes.push($createMentionNode(md))
       last = end
       last = end
     }
     }
@@ -465,26 +465,26 @@ const MessageInputInner = forwardRef<
             source:
             source:
               mention.type === "symbol"
               mention.type === "symbol"
                 ? {
                 ? {
-                    type: "symbol",
-                    text: {
-                      value: mention.display,
-                      start: mention.start,
-                      end: mention.end,
-                    },
-                    path: absolutePath,
-                    name: mention.metadata.name,
-                    range: mention.metadata.range,
-                    kind: mention.metadata.kind,
-                  }
+                  type: "symbol",
+                  text: {
+                    value: mention.display,
+                    start: mention.start,
+                    end: mention.end,
+                  },
+                  path: absolutePath,
+                  name: mention.metadata.name,
+                  range: mention.metadata.range,
+                  kind: mention.metadata.kind,
+                }
                 : {
                 : {
-                    type: "file",
-                    text: {
-                      value: mention.display,
-                      start: mention.start,
-                      end: mention.end,
-                    },
-                    path: absolutePath,
+                  type: "file",
+                  text: {
+                    value: mention.display,
+                    start: mention.start,
+                    end: mention.end,
                   },
                   },
+                  path: absolutePath,
+                },
           })
           })
         } else if (mention.type === "agent") {
         } else if (mention.type === "agent") {
           parts.push({
           parts.push({
@@ -591,9 +591,9 @@ const MessageInputInner = forwardRef<
       if (response.error) {
       if (response.error) {
         const errorMsg =
         const errorMsg =
           "data" in response.error &&
           "data" in response.error &&
-          response.error.data &&
-          typeof response.error.data === "object" &&
-          "message" in response.error.data
+            response.error.data &&
+            typeof response.error.data === "object" &&
+            "message" in response.error.data
             ? String(response.error.data.message)
             ? String(response.error.data.message)
             : "Failed to send message"
             : "Failed to send message"
         throw new Error(errorMsg)
         throw new Error(errorMsg)
@@ -1005,21 +1005,21 @@ const MessageInputInner = forwardRef<
     <>
     <>
       <footer className="border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 flex-shrink-0">
       <footer className="border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 flex-shrink-0">
         {/* Input area */}
         {/* Input area */}
-        <div className="px-3 pt-2 pb-1">
+        <div className="px-2 pt-1.5 pb-1">
           <div
           <div
             ref={containerRef}
             ref={containerRef}
-            className="relative border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 rounded"
+            className="relative modern-input bg-white dark:bg-gray-900"
           >
           >
             <RichTextPlugin
             <RichTextPlugin
               contentEditable={
               contentEditable={
                 // @ts-expect-error React 19 type compatibility
                 // @ts-expect-error React 19 type compatibility
                 <ContentEditable
                 <ContentEditable
                   ref={contentEditableRef}
                   ref={contentEditableRef}
-                  className="px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:outline-none min-h-[32px] max-h-[400px] overflow-y-auto"
+                  className="px-2 py-1.5 text-sm text-gray-900 dark:text-gray-100 focus:outline-none min-h-[32px] max-h-[400px] overflow-y-auto"
                   style={{ caretColor: "auto" }}
                   style={{ caretColor: "auto" }}
                   aria-placeholder="Ask anything (Cmd/Ctrl+Enter to send)"
                   aria-placeholder="Ask anything (Cmd/Ctrl+Enter to send)"
                   placeholder={
                   placeholder={
-                    <div className="absolute top-2 left-3 text-sm text-gray-400 dark:text-gray-500 pointer-events-none">
+                    <div className="absolute top-1.5 left-2 text-sm text-gray-400 dark:text-gray-500 pointer-events-none">
                       Ask anything (Cmd/Ctrl+Enter to send)
                       Ask anything (Cmd/Ctrl+Enter to send)
                     </div>
                     </div>
                   }
                   }
@@ -1035,7 +1035,7 @@ const MessageInputInner = forwardRef<
         </div>
         </div>
 
 
         {/* Toolbar */}
         {/* Toolbar */}
-        <div className="h-8 px-3 flex items-center justify-between border-t border-gray-100 dark:border-gray-800">
+        <div className="h-8 px-2 flex items-center justify-between border-t border-gray-100 dark:border-gray-800">
           <div className="flex items-center gap-1">
           <div className="flex items-center gap-1">
             {/* Retry button (visible when there's a failed message) */}
             {/* Retry button (visible when there's a failed message) */}
             {lastFailedMessage && (
             {lastFailedMessage && (
@@ -1071,7 +1071,7 @@ const MessageInputInner = forwardRef<
             {/* Add file button */}
             {/* Add file button */}
             <button
             <button
               onClick={handleFileSelect}
               onClick={handleFileSelect}
-              className="h-6 w-6 flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded disabled:opacity-50 disabled:cursor-not-allowed"
+              className="modern-icon-button w-6 h-6 flex items-center justify-center"
               disabled={isDisabled}
               disabled={isDisabled}
               title="Add file"
               title="Add file"
             >
             >
@@ -1099,7 +1099,7 @@ const MessageInputInner = forwardRef<
             <button
             <button
               onClick={() => setIsCompactConfirmOpen(true)}
               onClick={() => setIsCompactConfirmOpen(true)}
               disabled={isCompactDisabled}
               disabled={isCompactDisabled}
-              className="h-6 px-2 flex items-center gap-1 text-xs font-medium text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded border border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
+              className="modern-button modern-button-ghost h-6 px-2 text-xs"
               title="Compact session history"
               title="Compact session history"
             >
             >
               <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
               <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">

+ 5 - 5
packages/opencode/webgui/src/components/MessageList.tsx

@@ -82,7 +82,7 @@ function renderTextPart(part: Part, isUser: boolean, attachedParts?: Part[]) {
     return (
     return (
       <div
       <div
         key={part.id}
         key={part.id}
-        className="inline-block bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 shadow-sm text-sm text-gray-900 dark:text-gray-100"
+        className="inline-block modern-card px-3 py-2 text-sm text-gray-900 dark:text-gray-100 bg-gray-50 dark:bg-gray-800/50 border-transparent dark:border-gray-800"
       >
       >
         <div className="whitespace-pre-wrap" onCopy={handleCopy}>
         <div className="whitespace-pre-wrap" onCopy={handleCopy}>
           {mentions.length > 0 ? renderTextWithMentions(text, mentions) : text}
           {mentions.length > 0 ? renderTextWithMentions(text, mentions) : text}
@@ -365,7 +365,7 @@ function MessageRow({ message, onFork, onRevert, revertBusy, sessionID }: Messag
         <div className="absolute left-1/2 -translate-x-1/2 top-0 flex gap-2">
         <div className="absolute left-1/2 -translate-x-1/2 top-0 flex gap-2">
           <button
           <button
             onClick={() => onFork(message.info.id)}
             onClick={() => onFork(message.info.id)}
-            className="p-1 text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400"
+            className="modern-icon-button"
             title="Fork session at this message"
             title="Fork session at this message"
           >
           >
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
             <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -379,7 +379,7 @@ function MessageRow({ message, onFork, onRevert, revertBusy, sessionID }: Messag
           </button>
           </button>
           <button
           <button
             onClick={() => onRevert(message.info.id)}
             onClick={() => onRevert(message.info.id)}
-            className="p-1 text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 disabled:opacity-50 disabled:cursor-not-allowed"
+            className="modern-icon-button hover:text-red-600 dark:hover:text-red-400"
             title="Undo from this message (revert)"
             title="Undo from this message (revert)"
             disabled={revertBusy}
             disabled={revertBusy}
           >
           >
@@ -610,7 +610,7 @@ export function MessageList({ sessionID, onUndoToInput }: MessageListProps) {
         rows.push(
         rows.push(
           <div
           <div
             key="revert-summary"
             key="revert-summary"
-            className="text-xs px-3 py-2 rounded-md bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300 border border-gray-200 dark:border-gray-700"
+            className="text-xs px-2 py-1.5 rounded-md bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300 border border-gray-200 dark:border-gray-700"
           >
           >
             <div className="flex items-center justify-between">
             <div className="flex items-center justify-between">
               <span className="truncate mr-2">Messages and changes after this point are hidden (reverted).</span>
               <span className="truncate mr-2">Messages and changes after this point are hidden (reverted).</span>
@@ -691,7 +691,7 @@ export function MessageList({ sessionID, onUndoToInput }: MessageListProps) {
         <div className="space-y-2">
         <div className="space-y-2">
           {/* Revert banner (pinned to top of scroll area) */}
           {/* Revert banner (pinned to top of scroll area) */}
           {currentSession?.revert?.messageID && (
           {currentSession?.revert?.messageID && (
-            <div className="sticky top-0 z-10 flex items-center justify-between text-xs px-3 py-2 rounded-md bg-amber-50 text-amber-800 dark:bg-amber-900/30 dark:text-amber-200 border border-amber-200 dark:border-amber-800">
+            <div className="sticky top-0 z-10 flex items-center justify-between text-xs px-2 py-1.5 rounded-md bg-amber-50 text-amber-800 dark:bg-amber-900/30 dark:text-amber-200 border border-amber-200 dark:border-amber-800">
               <div className="truncate mr-2">Changes after a previous message were undone.</div>
               <div className="truncate mr-2">Changes after a previous message were undone.</div>
               <div className="flex gap-2">
               <div className="flex gap-2">
                 <button
                 <button

+ 15 - 15
packages/opencode/webgui/src/components/ModelSelector.tsx

@@ -166,18 +166,19 @@ export function ModelSelector({ selectedProviderId, selectedModelId, onSelect, d
                         <button
                         <button
                           key={`${item.provider_id}:${item.model_id}:${item.last_used}`}
                           key={`${item.provider_id}:${item.model_id}:${item.last_used}`}
                           onClick={() => handleSelect(item.provider_id, item.model_id)}
                           onClick={() => handleSelect(item.provider_id, item.model_id)}
-                          className={`w-full px-3 py-2 text-xs text-left hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-between ${
-                            isSelected
+                          className={`w-full px-3 py-2 text-xs text-left hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-between ${isSelected
                               ? "bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400"
                               ? "bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400"
                               : "text-gray-900 dark:text-gray-100"
                               : "text-gray-900 dark:text-gray-100"
-                          }`}
+                            }`}
                         >
                         >
-                          <div className="flex flex-col gap-0.5 flex-1 min-w-0">
+                          <div className="flex items-center gap-2 flex-1 min-w-0">
                             <span className="font-medium truncate">{item.model_id}</span>
                             <span className="font-medium truncate">{item.model_id}</span>
-                            <div className="flex items-center gap-2 text-[10px] text-gray-500 dark:text-gray-400">
-                              <span className="truncate max-w-[10rem]">{item.provider_id}</span>
-                              <span>{new Date(item.last_used).toLocaleDateString()}</span>
-                            </div>
+                            <span className="text-[10px] text-gray-400 dark:text-gray-500 flex-shrink-0">
+                              {new Date(item.last_used).toLocaleDateString()}
+                            </span>
+                            <span className="text-[10px] text-gray-400 dark:text-gray-500 truncate max-w-[6rem]">
+                              {item.provider_id}
+                            </span>
                           </div>
                           </div>
                           {isSelected && (
                           {isSelected && (
                             <svg className="w-4 h-4 ml-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
                             <svg className="w-4 h-4 ml-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
@@ -212,23 +213,22 @@ export function ModelSelector({ selectedProviderId, selectedModelId, onSelect, d
                           <button
                           <button
                             key={modelId}
                             key={modelId}
                             onClick={() => handleSelect(provider.id, modelId)}
                             onClick={() => handleSelect(provider.id, modelId)}
-                            className={`w-full px-3 py-2 text-xs text-left hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-between ${
-                              isSelected
+                            className={`w-full px-3 py-2 text-xs text-left hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-between ${isSelected
                                 ? "bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400"
                                 ? "bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400"
                                 : "text-gray-900 dark:text-gray-100"
                                 : "text-gray-900 dark:text-gray-100"
-                            }`}
+                              }`}
                           >
                           >
-                            <div className="flex flex-col gap-0.5 flex-1 min-w-0">
+                            <div className="flex items-center gap-2 flex-1 min-w-0">
                               <span className="font-medium truncate">{model.name}</span>
                               <span className="font-medium truncate">{model.name}</span>
-                              <div className="flex items-center gap-2 text-[10px] text-gray-500 dark:text-gray-400">
+                              <div className="flex items-center gap-1.5 text-[10px] text-gray-400 dark:text-gray-500 flex-shrink-0">
                                 <span>{new Date(model.release_date).toLocaleDateString()}</span>
                                 <span>{new Date(model.release_date).toLocaleDateString()}</span>
                                 {model.reasoning && (
                                 {model.reasoning && (
-                                  <span className="px-1 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded">
+                                  <span className="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded text-[9px] leading-none">
                                     reasoning
                                     reasoning
                                   </span>
                                   </span>
                                 )}
                                 )}
                                 {isDefault && (
                                 {isDefault && (
-                                  <span className="px-1 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 rounded">
+                                  <span className="px-1 py-0.5 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 rounded text-[9px] leading-none">
                                     default
                                     default
                                   </span>
                                   </span>
                                 )}
                                 )}

+ 13 - 14
packages/opencode/webgui/src/components/SettingsPanel.tsx

@@ -187,14 +187,14 @@ export function SettingsPanel({ isOpen, onClose }: SettingsPanelProps) {
 
 
   return (
   return (
     <>
     <>
-      <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
-        <div className="bg-white dark:bg-gray-900 rounded-lg shadow-lg w-full max-w-3xl mx-4 border border-gray-200 dark:border-gray-800 max-h-[90vh] flex flex-col">
+      <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
+        <div className="modern-card w-full max-w-3xl mx-4 max-h-[90vh] flex flex-col shadow-2xl">
           {/* Header */}
           {/* Header */}
-          <div className="px-4 py-3 border-b border-gray-200 dark:border-gray-800 flex items-center justify-between">
-            <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Settings</h2>
+          <div className="px-3 py-2 border-b border-gray-200 dark:border-gray-800 flex items-center justify-between">
+            <h2 className="text-base font-semibold text-gray-900 dark:text-gray-100">Settings</h2>
             <button
             <button
               onClick={handleClose}
               onClick={handleClose}
-              className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
+              className="modern-icon-button"
               title="Close"
               title="Close"
             >
             >
               <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
               <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -210,11 +210,10 @@ export function SettingsPanel({ isOpen, onClose }: SettingsPanelProps) {
                 <button
                 <button
                   key={tab.id}
                   key={tab.id}
                   onClick={() => setActiveTab(tab.id)}
                   onClick={() => setActiveTab(tab.id)}
-                  className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
-                    activeTab === tab.id
-                      ? "border-blue-600 text-blue-600 dark:border-blue-500 dark:text-blue-500"
-                      : "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
-                  }`}
+                  className={`px-3 py-1.5 text-sm font-medium border-b-2 transition-colors ${activeTab === tab.id
+                    ? "border-blue-600 text-blue-600 dark:border-blue-500 dark:text-blue-500"
+                    : "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
+                    }`}
                 >
                 >
                   <span className="mr-1.5">{tab.icon}</span>
                   <span className="mr-1.5">{tab.icon}</span>
                   {tab.label}
                   {tab.label}
@@ -224,7 +223,7 @@ export function SettingsPanel({ isOpen, onClose }: SettingsPanelProps) {
           </div>
           </div>
 
 
           {/* Content */}
           {/* Content */}
-          <div className="flex-1 overflow-y-auto px-4 py-4">
+          <div className="flex-1 overflow-y-auto px-3 py-3">
             {isLoading ? (
             {isLoading ? (
               <div className="flex items-center justify-center py-12">
               <div className="flex items-center justify-center py-12">
                 <div className="text-gray-500 dark:text-gray-400">Loading settings...</div>
                 <div className="text-gray-500 dark:text-gray-400">Loading settings...</div>
@@ -264,7 +263,7 @@ export function SettingsPanel({ isOpen, onClose }: SettingsPanelProps) {
           </div>
           </div>
 
 
           {/* Footer */}
           {/* Footer */}
-          <div className="px-4 py-3 bg-gray-50 dark:bg-gray-950 border-t border-gray-200 dark:border-gray-800 flex items-center justify-between">
+          <div className="px-3 py-2 bg-gray-50 dark:bg-gray-950 border-t border-gray-200 dark:border-gray-800 flex items-center justify-between">
             <div>
             <div>
               {successMessage && <span className="text-sm text-green-600 dark:text-green-400">{successMessage}</span>}
               {successMessage && <span className="text-sm text-green-600 dark:text-green-400">{successMessage}</span>}
             </div>
             </div>
@@ -272,14 +271,14 @@ export function SettingsPanel({ isOpen, onClose }: SettingsPanelProps) {
               <button
               <button
                 onClick={handleClose}
                 onClick={handleClose}
                 disabled={isSaving}
                 disabled={isSaving}
-                className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
+                className="modern-button modern-button-secondary"
               >
               >
                 Cancel
                 Cancel
               </button>
               </button>
               <button
               <button
                 onClick={handleSave}
                 onClick={handleSave}
                 disabled={isSaving || isLoading || !hasUnsavedChanges()}
                 disabled={isSaving || isLoading || !hasUnsavedChanges()}
-                className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800 rounded disabled:opacity-50 disabled:cursor-not-allowed"
+                className="modern-button modern-button-primary"
               >
               >
                 {isSaving ? "Saving..." : "Save Changes"}
                 {isSaving ? "Saving..." : "Save Changes"}
               </button>
               </button>

+ 191 - 139
packages/opencode/webgui/src/components/settings/ApiKeysTab.tsx

@@ -41,6 +41,7 @@ export function ApiKeysTab({
   const [providerToDelete, setProviderToDelete] = useState<string | null>(null)
   const [providerToDelete, setProviderToDelete] = useState<string | null>(null)
   const [isDeleting, setIsDeleting] = useState(false)
   const [isDeleting, setIsDeleting] = useState(false)
   const { markProvidersDirty } = useProviders()
   const { markProvidersDirty } = useProviders()
+  const [expandedProvider, setExpandedProvider] = useState<string | null>(null)
 
 
   // Filter providers to only show configured ones + the one currently being added
   // Filter providers to only show configured ones + the one currently being added
   const displayedProviders = useMemo(() => {
   const displayedProviders = useMemo(() => {
@@ -224,11 +225,13 @@ export function ApiKeysTab({
   const handleAddProvider = (providerId: string) => {
   const handleAddProvider = (providerId: string) => {
     if (!providerId) return
     if (!providerId) return
     setSelectedProviderToAdd(providerId)
     setSelectedProviderToAdd(providerId)
+    setExpandedProvider(providerId)
     // Optimistically add to displayed list via selectedProviderToAdd state
     // Optimistically add to displayed list via selectedProviderToAdd state
     // It will be permanently added to configuredProviders when saved (API key) or logged in (OAuth)
     // It will be permanently added to configuredProviders when saved (API key) or logged in (OAuth)
   }
   }
 
 
-  const handleDeleteProvider = (providerId: string) => {
+  const handleDeleteProvider = (providerId: string, e: React.MouseEvent) => {
+    e.stopPropagation()
     setProviderToDelete(providerId)
     setProviderToDelete(providerId)
   }
   }
 
 
@@ -265,30 +268,30 @@ export function ApiKeysTab({
       </div>
       </div>
 
 
       {/* Add Provider Dropdown */}
       {/* Add Provider Dropdown */}
-      <div className="relative mb-6" ref={dropdownRef}>
+      <div className="relative mb-4" ref={dropdownRef}>
         <button
         <button
           onClick={toggle}
           onClick={toggle}
-          className="w-full flex items-center justify-between px-3 py-2 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
+          className="w-full flex items-center justify-between px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 hover:border-gray-400 dark:hover:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors text-sm shadow-sm"
         >
         >
-          <span>Add a provider...</span>
+          <span className="font-medium">Add a provider...</span>
           <svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
           <svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
           </svg>
           </svg>
         </button>
         </button>
 
 
         {isOpen && (
         {isOpen && (
-          <div className="absolute top-full left-0 right-0 mt-1 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50 max-h-60 overflow-hidden flex flex-col">
-            <div className="p-2 border-b border-gray-200 dark:border-gray-700">
+          <div className="absolute top-full left-0 right-0 mt-1 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl z-50 max-h-60 overflow-hidden flex flex-col ring-1 ring-black/5">
+            <div className="p-2 border-b border-gray-100 dark:border-gray-800">
               <input
               <input
                 type="text"
                 type="text"
                 value={searchTerm}
                 value={searchTerm}
                 onChange={(e) => setSearchTerm(e.target.value)}
                 onChange={(e) => setSearchTerm(e.target.value)}
                 placeholder="Search providers..."
                 placeholder="Search providers..."
-                className="w-full px-2 py-1 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
+                className="w-full px-2 py-1.5 text-sm bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
                 autoFocus
                 autoFocus
               />
               />
             </div>
             </div>
-            <div className="overflow-y-auto flex-1">
+            <div className="overflow-y-auto flex-1 p-1">
               {filteredAvailableProviders.length === 0 ? (
               {filteredAvailableProviders.length === 0 ? (
                 <div className="p-3 text-sm text-gray-500 dark:text-gray-400 text-center">No providers found</div>
                 <div className="p-3 text-sm text-gray-500 dark:text-gray-400 text-center">No providers found</div>
               ) : (
               ) : (
@@ -296,7 +299,7 @@ export function ApiKeysTab({
                   <button
                   <button
                     key={p.id}
                     key={p.id}
                     onClick={() => handleSelectProvider(p.id)}
                     onClick={() => handleSelectProvider(p.id)}
-                    className="w-full px-3 py-2 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-900 dark:text-gray-100"
+                    className="w-full px-3 py-2 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-md transition-colors"
                   >
                   >
                     {p.name}
                     {p.name}
                   </button>
                   </button>
@@ -307,150 +310,199 @@ export function ApiKeysTab({
         )}
         )}
       </div>
       </div>
 
 
-      {displayedProviders.length === 0 ? (
-        <div className="text-sm text-gray-500 dark:text-gray-400 text-center py-8 border border-dashed border-gray-300 dark:border-gray-700 rounded">
-          No providers configured. Add one above.
-        </div>
-      ) : (
-        displayedProviders.map((provider) => {
-          const providerMethods = methods[provider.id] || []
-          const oauthMethodIndex = providerMethods.findIndex((m) => m.type === "oauth")
-          const hasOAuth = oauthMethodIndex !== -1
-          const isLoading = loadingMethods[provider.id]
-          const isTemporary = !configuredProviders.includes(provider.id)
-
-          return (
-            <div key={provider.id} className="border border-gray-200 dark:border-gray-700 rounded p-3 relative group">
-              <button
-                onClick={() => handleDeleteProvider(provider.id)}
-                className="absolute top-2 right-2 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
-                title="Remove provider"
+      <div className="space-y-2">
+        {displayedProviders.length === 0 ? (
+          <div className="text-sm text-gray-500 dark:text-gray-400 text-center py-8 border border-dashed border-gray-200 dark:border-gray-800 rounded-lg bg-gray-50/50 dark:bg-gray-900/50">
+            No providers configured. Add one above.
+          </div>
+        ) : (
+          displayedProviders.map((provider) => {
+            const providerMethods = methods[provider.id] || []
+            const oauthMethodIndex = providerMethods.findIndex((m) => m.type === "oauth")
+            const hasOAuth = oauthMethodIndex !== -1
+            const isLoading = loadingMethods[provider.id]
+            const isTemporary = !configuredProviders.includes(provider.id)
+            const isExpanded = expandedProvider === provider.id
+            const isConnected = configuredProviders.includes(provider.id)
+
+            return (
+              <div
+                key={provider.id}
+                className={`border transition-all duration-200 rounded-lg overflow-hidden ${isExpanded
+                    ? "border-blue-200 dark:border-blue-900 bg-blue-50/30 dark:bg-blue-900/10 ring-1 ring-blue-500/20"
+                    : "border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 hover:border-gray-300 dark:hover:border-gray-700"
+                  }`}
               >
               >
-                <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
-                </svg>
-              </button>
-
-              <div className="flex items-center justify-between mb-2 pr-6">
-                <div className="flex items-center gap-2">
-                  <label className="text-sm font-medium text-gray-700 dark:text-gray-300">{provider.name}</label>
-                  {isTemporary && (
-                    <span className="text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-1.5 py-0.5 rounded">
-                      New
+                <div
+                  className="flex items-center justify-between px-3 py-2.5 cursor-pointer select-none"
+                  onClick={() => setExpandedProvider(isExpanded ? null : provider.id)}
+                >
+                  <div className="flex items-center gap-3">
+                    <div className={`w-2 h-2 rounded-full ${isConnected ? "bg-green-500" : "bg-gray-300 dark:bg-gray-600"}`} />
+                    <span className="text-sm font-medium text-gray-900 dark:text-gray-100">{provider.name}</span>
+                    {isTemporary && (
+                      <span className="text-[10px] font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300 px-1.5 py-0.5 rounded-full">
+                        New
+                      </span>
+                    )}
+                  </div>
+                  <div className="flex items-center gap-2">
+                    <span className="text-xs text-gray-500 dark:text-gray-400">
+                      {isConnected ? "Connected" : "Not Configured"}
                     </span>
                     </span>
-                  )}
+                    <button
+                      onClick={(e) => handleDeleteProvider(provider.id, e)}
+                      className="p-1 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors"
+                      title="Remove provider"
+                    >
+                      <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
+                      </svg>
+                    </button>
+                    <svg
+                      className={`w-4 h-4 text-gray-400 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}`}
+                      fill="none"
+                      stroke="currentColor"
+                      viewBox="0 0 24 24"
+                    >
+                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
+                    </svg>
+                  </div>
                 </div>
                 </div>
-                {provider.env.length > 0 && (
-                  <span className="text-xs text-gray-500 dark:text-gray-400">{provider.env[0]}</span>
-                )}
-              </div>
 
 
-              <div className="space-y-2">
-                {isLoading && <div className="text-xs text-gray-400">Loading auth methods...</div>}
-
-                {hasOAuth && (
-                  <div className="flex flex-col gap-2">
-                    <div className="flex items-center gap-2">
-                      <button
-                        onClick={() => handleOAuthLogin(provider.id, oauthMethodIndex)}
-                        disabled={!!authStatus[provider.id] && authStatus[provider.id] !== "Waiting for code..."}
-                        className="px-4 py-2 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 rounded text-sm font-medium hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors disabled:opacity-50"
-                      >
-                        {providerMethods[oauthMethodIndex].label || `Login with ${provider.name}`}
-                      </button>
-                      {authStatus[provider.id] && (
-                        <>
-                          <span className="text-sm text-blue-600 dark:text-blue-400 animate-pulse">
-                            {authStatus[provider.id]}
-                          </span>
-                          {(authStatus[provider.id].startsWith("Waiting") ||
-                            authStatus[provider.id] === "Initializing...") &&
-                            !manualCodeState && (
+                {isExpanded && (
+                  <div className="px-3 pb-3 pt-0 border-t border-blue-100 dark:border-blue-900/30 mt-2">
+                    <div className="pt-3 space-y-3">
+                      {isLoading && <div className="text-xs text-gray-400 animate-pulse">Loading auth methods...</div>}
+
+                      {hasOAuth && (
+                        <div className="space-y-2">
+                          <div className="flex items-center gap-2">
+                            <button
+                              onClick={() => handleOAuthLogin(provider.id, oauthMethodIndex)}
+                              disabled={!!authStatus[provider.id] && authStatus[provider.id] !== "Waiting for code..."}
+                              className="px-3 py-1.5 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 rounded-md text-xs font-medium hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors disabled:opacity-50 shadow-sm"
+                            >
+                              {providerMethods[oauthMethodIndex].label || `Login with ${provider.name}`}
+                            </button>
+                            {authStatus[provider.id] && (
+                              <div className="flex items-center gap-2">
+                                <span className="text-xs text-blue-600 dark:text-blue-400 animate-pulse">
+                                  {authStatus[provider.id]}
+                                </span>
+                                {(authStatus[provider.id].startsWith("Waiting") ||
+                                  authStatus[provider.id] === "Initializing...") &&
+                                  !manualCodeState && (
+                                    <button
+                                      onClick={() => handleCancel(provider.id)}
+                                      className="px-2 py-1 text-xs border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-400"
+                                    >
+                                      Cancel
+                                    </button>
+                                  )}
+                              </div>
+                            )}
+                          </div>
+
+                          {manualCodeState?.providerId === provider.id && (
+                            <div className="flex gap-2 p-2 bg-gray-50 dark:bg-gray-800/50 rounded-md border border-gray-100 dark:border-gray-800">
+                              <input
+                                type="text"
+                                value={manualCodeInput}
+                                onChange={(e) => setManualCodeInput(e.target.value)}
+                                placeholder="Paste authorization code here"
+                                className="flex-1 px-2 py-1.5 border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-xs"
+                              />
                               <button
                               <button
-                                onClick={() => handleCancel(provider.id)}
-                                className="px-2 py-1 text-xs border border-gray-300 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400"
+                                onClick={handleManualCodeSubmit}
+                                disabled={!manualCodeInput}
+                                className="px-2 py-1.5 bg-blue-600 text-white rounded text-xs font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
+                              >
+                                Submit
+                              </button>
+                              <button
+                                onClick={() => {
+                                  setManualCodeState(null)
+                                  setManualCodeInput("")
+                                  setAuthStatus((prev) => ({ ...prev, [provider.id]: "" }))
+                                }}
+                                className="px-2 py-1.5 border border-gray-200 dark:border-gray-700 rounded text-xs hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400"
                               >
                               >
                                 Cancel
                                 Cancel
                               </button>
                               </button>
-                            )}
-                        </>
+                            </div>
+                          )}
+                        </div>
                       )}
                       )}
-                    </div>
 
 
-                    {manualCodeState?.providerId === provider.id && (
-                      <div className="flex gap-2 mt-2">
-                        <input
-                          type="text"
-                          value={manualCodeInput}
-                          onChange={(e) => setManualCodeInput(e.target.value)}
-                          placeholder="Paste authorization code here"
-                          className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-sm"
-                        />
-                        <button
-                          onClick={handleManualCodeSubmit}
-                          disabled={!manualCodeInput}
-                          className="px-3 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
-                        >
-                          Submit
-                        </button>
-                        <button
-                          onClick={() => {
-                            setManualCodeState(null)
-                            setManualCodeInput("")
-                            setAuthStatus((prev) => ({ ...prev, [provider.id]: "" }))
-                          }}
-                          className="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700"
-                        >
-                          Cancel
-                        </button>
-                      </div>
-                    )}
-                  </div>
-                )}
-
-                {/* Show API Key input if no OAuth, or as alternative */}
-                {(!hasOAuth || providerMethods.some((m) => m.type === "api")) && !isLoading && (
-                  <>
-                    {hasOAuth && (
-                      <div className="relative my-3">
-                        <div className="absolute inset-0 flex items-center">
-                          <span className="w-full border-t border-gray-200 dark:border-gray-700" />
-                        </div>
-                        <div className="relative flex justify-center text-xs uppercase">
-                          <span className="bg-white dark:bg-gray-900 px-2 text-gray-500">Or use API Key</span>
+                      {/* Show API Key input if no OAuth, or as alternative */}
+                      {(!hasOAuth || providerMethods.some((m) => m.type === "api")) && !isLoading && (
+                        <div className="space-y-2">
+                          {hasOAuth && (
+                            <div className="relative">
+                              <div className="absolute inset-0 flex items-center">
+                                <span className="w-full border-t border-gray-200 dark:border-gray-700" />
+                              </div>
+                              <div className="relative flex justify-center text-[10px] uppercase tracking-wider">
+                                <span className="bg-white dark:bg-gray-900 px-2 text-gray-400">Or use API Key</span>
+                              </div>
+                            </div>
+                          )}
+                          <div className="flex gap-2">
+                            <div className="relative flex-1">
+                              <input
+                                type={showApiKeys[provider.id] ? "text" : "password"}
+                                value={apiKeys[provider.id] || ""}
+                                onChange={(e) => {
+                                  setApiKeys({ ...apiKeys, [provider.id]: e.target.value })
+                                }}
+                                placeholder={`Enter ${provider.name} API key`}
+                                className="w-full pl-3 pr-8 py-1.5 border border-gray-200 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-xs shadow-sm"
+                              />
+                              <button
+                                onClick={() => setShowApiKeys({ ...showApiKeys, [provider.id]: !showApiKeys[provider.id] })}
+                                className="absolute right-1 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded"
+                                title={showApiKeys[provider.id] ? "Hide" : "Show"}
+                              >
+                                {showApiKeys[provider.id] ? (
+                                  <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                    <path
+                                      strokeLinecap="round"
+                                      strokeLinejoin="round"
+                                      strokeWidth={2}
+                                      d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
+                                    />
+                                  </svg>
+                                ) : (
+                                  <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                    <path
+                                      strokeLinecap="round"
+                                      strokeLinejoin="round"
+                                      strokeWidth={2}
+                                      d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
+                                    />
+                                    <path
+                                      strokeLinecap="round"
+                                      strokeLinejoin="round"
+                                      strokeWidth={2}
+                                      d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
+                                    />
+                                  </svg>
+                                )}
+                              </button>
+                            </div>
+                          </div>
                         </div>
                         </div>
-                      </div>
-                    )}
-                    <div className="flex gap-2">
-                      <input
-                        type={showApiKeys[provider.id] ? "text" : "password"}
-                        value={apiKeys[provider.id] || ""}
-                        onChange={(e) => {
-                          setApiKeys({ ...apiKeys, [provider.id]: e.target.value })
-                          // If user starts typing, ensure it's treated as being added (though save is required to persist)
-                          if (!configuredProviders.includes(provider.id)) {
-                            // We don't add to configuredProviders yet, that happens on save
-                            // But we keep it in selectedProviderToAdd so it stays visible
-                          }
-                        }}
-                        placeholder={`Enter ${provider.name} API key`}
-                        className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-sm"
-                      />
-                      <button
-                        onClick={() => setShowApiKeys({ ...showApiKeys, [provider.id]: !showApiKeys[provider.id] })}
-                        className="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
-                        title={showApiKeys[provider.id] ? "Hide" : "Show"}
-                      >
-                        {showApiKeys[provider.id] ? "👁️" : "👁️‍🗨️"}
-                      </button>
+                      )}
                     </div>
                     </div>
-                  </>
+                  </div>
                 )}
                 )}
               </div>
               </div>
-            </div>
-          )
-        })
-      )}
+            )
+          })
+        )}
+      </div>
+
       <ConfirmModal
       <ConfirmModal
         isOpen={!!providerToDelete}
         isOpen={!!providerToDelete}
         onClose={() => setProviderToDelete(null)}
         onClose={() => setProviderToDelete(null)}

+ 2 - 2
packages/opencode/webgui/src/components/settings/ModelsTab.tsx

@@ -19,7 +19,7 @@ export function ModelsTab({ formData, setFormData, providers, configuredProvider
           value={formData.model || ""}
           value={formData.model || ""}
           onChange={(e) => setFormData({ ...formData, model: e.target.value })}
           onChange={(e) => setFormData({ ...formData, model: e.target.value })}
           placeholder="e.g., anthropic/claude-sonnet-4-5"
           placeholder="e.g., anthropic/claude-sonnet-4-5"
-          className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-sm"
+          className="modern-input w-full font-mono text-sm"
         />
         />
         <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
         <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
           Format: provider/model (e.g., anthropic/claude-sonnet-4-5)
           Format: provider/model (e.g., anthropic/claude-sonnet-4-5)
@@ -33,7 +33,7 @@ export function ModelsTab({ formData, setFormData, providers, configuredProvider
           value={formData.small_model || ""}
           value={formData.small_model || ""}
           onChange={(e) => setFormData({ ...formData, small_model: e.target.value })}
           onChange={(e) => setFormData({ ...formData, small_model: e.target.value })}
           placeholder="e.g., anthropic/claude-haiku-3-5"
           placeholder="e.g., anthropic/claude-haiku-3-5"
-          className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-sm"
+          className="modern-input w-full font-mono text-sm"
         />
         />
         <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">Used for tasks like title generation</p>
         <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">Used for tasks like title generation</p>
       </div>
       </div>

+ 51 - 3
packages/opencode/webgui/src/index.css

@@ -16,8 +16,8 @@ body {
 
 
 /* Scrollbar styling */
 /* Scrollbar styling */
 ::-webkit-scrollbar {
 ::-webkit-scrollbar {
-  width: 8px;
-  height: 8px;
+  width: 6px;
+  height: 6px;
 }
 }
 
 
 ::-webkit-scrollbar-track {
 ::-webkit-scrollbar-track {
@@ -26,7 +26,7 @@ body {
 
 
 ::-webkit-scrollbar-thumb {
 ::-webkit-scrollbar-thumb {
   background: rgba(156, 163, 175, 0.3);
   background: rgba(156, 163, 175, 0.3);
-  border-radius: 4px;
+  border-radius: 3px;
 }
 }
 
 
 ::-webkit-scrollbar-thumb:hover {
 ::-webkit-scrollbar-thumb:hover {
@@ -41,3 +41,51 @@ body {
 .dark ::-webkit-scrollbar-thumb:hover {
 .dark ::-webkit-scrollbar-thumb:hover {
   background: rgba(107, 114, 128, 0.5);
   background: rgba(107, 114, 128, 0.5);
 }
 }
+
+/* Utility for compact spacing */
+.compact-spacing {
+  --header-height: 2.25rem;
+  /* 36px */
+  --input-padding: 0.5rem;
+}
+
+/* Modern UI Utilities */
+.modern-border {
+  @apply border border-gray-200 dark:border-gray-800;
+}
+
+.modern-shadow {
+  @apply shadow-sm hover:shadow transition-shadow duration-200;
+}
+
+.modern-input {
+  @apply border-gray-200 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm;
+}
+
+.modern-button {
+  @apply px-3 py-1.5 rounded-md font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm active:scale-95 flex items-center justify-center gap-2;
+}
+
+.modern-button-primary {
+  @apply bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 border border-transparent;
+}
+
+.modern-button-secondary {
+  @apply bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700;
+}
+
+.modern-button-ghost {
+  @apply text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100 shadow-none border border-transparent;
+}
+
+.modern-button-danger {
+  @apply bg-red-600 text-white hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-700 border border-transparent;
+}
+
+.modern-card {
+  @apply bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-sm;
+}
+
+.modern-icon-button {
+  @apply p-1.5 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100 transition-all duration-200;
+}