diff --git a/.opencode/skills/settings-ui-patterns/SKILL.md b/.opencode/skills/settings-ui-patterns/SKILL.md new file mode 100644 index 00000000..0fc9276b --- /dev/null +++ b/.opencode/skills/settings-ui-patterns/SKILL.md @@ -0,0 +1,238 @@ +--- +name: settings-ui-patterns +description: Use when creating or modifying UI components, styling, or visual elements related to Settings in OpenChamber. +license: MIT +compatibility: opencode +--- + +# Settings UI Patterns Skill + +## Purpose +This skill provides instructions for creating or redesigning Settings pages, informational panels, and configuration interfaces within the OpenChamber application. + +## Current Canonical Look (2026) +Use this as source of truth for new settings UI work. + +- **Flat hierarchy first**: Prefer spacing + typography hierarchy over boxed backgrounds. +- **No unnecessary wrappers**: Avoid extra section wrappers that mix unrelated controls. +- **No redundant section titles**: Do not add headers like `Theme Preferences` or `Scaling & Layout` when controls are already self-explanatory. +- **Compact controls**: Option chips and radio rows should be dense, not tall. +- **Left-leading state icon**: Radio/checkbox state icon appears before text. +- **Subtle state contrast**: Inactive radio labels should be visibly dimmer than active labels. +- **Minimal row chrome**: Avoid row hover/background highlighting by default; keep only where explicitly needed. + +## Typography Guidelines +Always utilize the standard OpenChamber typography classes defined in `packages/ui/src/lib/typography.ts`. + +- **Page Title**: Use `typography-ui-header font-semibold text-foreground` for the top-most title of a settings page/dialog. +- **Section Header**: Use `typography-ui-header font-medium text-foreground` for settings sections (e.g. `Notification Events`, `Session Defaults`). +- **Control Group Header**: Use `typography-ui-header font-medium text-foreground` (or `font-normal` if it reads too loud) for grouped controls inside a section (e.g. `Default Tool Output`, `Diff Layout`). +- **Values / Primary Text**: Use `typography-ui-label text-foreground`. Add `tabular-nums` if displaying numbers or stats to ensure vertical alignment. +- **Option Labels**: Use non-bold label text in compact option controls (`font-normal` when needed to override). +- **Meta / Helper Text**: Use `typography-meta text-muted-foreground` or `typography-small text-muted-foreground` for supplemental text. + +## Layout and Spacing Patterns + +### 1. Main Backgrounds +Main wrappers should generally use `bg-background` or `bg-[var(--surface-background)]`. Ensure adequate padding (e.g., `px-5 py-6` or `p-6`). + +### 2. Subsection Grouping +Group related controls with vertical spacing, not mandatory cards. + +- Use `space-y-3` between logical subsections. +- Use `p-2` for subsection internal padding. +- Avoid adding `bg-[var(--surface-elevated)]` unless there is a clear reason. +- Avoid extra row decorations (`rounded-md`, hover fills) unless there is explicit UX value. + +### 3. Header-to-Content Hierarchy (critical) +When removing cards/background wrappers, spacing must be rebalanced so header ownership stays clear. + +- Keep **section-to-section spacing larger** than **header-to-own-content spacing**. +- Typical pattern: + - header wrapper `mb-1 px-1` + - content wrapper `pt-0 pb-2 px-2` + - outer section spacing `mb-8` +- Do not leave legacy `mb-3` style gaps after flattening a section; it makes headers look detached. + +### 4. Headerless Blocks (when context is obvious) +If the page title already provides enough context, remove redundant local headers and place controls directly below the title. + +- Example: project page identity controls can sit directly under project name/path. +- Tighten top gap for this pattern (e.g. top header `mb-4` instead of larger section spacing). + +```tsx +
+
...
+
...
+
+``` + +## Structural Patterns + +### 1. Segmented Option Buttons (compact) +Use for short option sets where button-style segmented choice reads best (e.g. Default Tool Output). + +```tsx +
+ + Collapsed + +
+``` + +### 2. Radio Option Lists (compact rows) +Use for mutually exclusive mode/layout settings (e.g. Diff Layout, Diff View Mode). + +- Use shared `Radio` component from `@/components/ui/radio`. +- Icon first, label second. +- Row container compact: `py-0.5`. +- Inactive label can use `text-foreground/50`. + +```tsx +
+
+ + Dynamic +
+
+``` + +### 3. Checkbox Setting Rows +Use shared `Checkbox` component from `@/components/ui/checkbox` for boolean toggles. + +- Icon first, text immediately after (`gap-2`). +- Typical row spacing for checkbox rows: `py-1.5`. +- Keep row click and keyboard toggle support. +- Prefer checkbox over binary show/hide button pairs for pure boolean state. + +```tsx +
+ + Show Dotfiles +
+``` + +### 4. Invisible Two-Column Alignment +Use consistent label/control columns across settings rows so controls align on a shared vertical line. + +- Desktop row pattern: `flex items-center gap-8` +- Label column width: `w-56 shrink-0` +- Control cluster: `w-fit` + +```tsx +
+ Interface Font Size +
...
+
+``` + +#### Disabled control rule +If a control is unavailable, disable the control only. Do not dim the label row by default. + +#### Width-matching rule +When matching visual widths across different rows, compare full row footprint (control + adjacent action buttons), not just input width. + +### 5. Theme Row Composition +For theme controls in Appearance: + +- `Color Mode` header on first line; option chips below it. +- `Light Theme` and `Dark Theme` on one row where possible, wrapping on small widths. +- Keep selectors near labels and aligned to existing column rhythm. +- Replace persistent helper text with an info tooltip icon near the related action. + +```tsx +
+
Light Theme ...
+
Dark Theme ...
+
+``` + +### 6. Numeric Controls in Settings +Use compact stepper input (`- value +`) plus reset button. + +- Prefer shared `NumberInput` stepper style over slider + numeric combo in dense settings pages. +- Keep reset button adjacent to control (`gap-2`). +- Avoid using Tailwind `overflow-hidden` on mobile for controls; `packages/ui/src/styles/mobile.css` forces `.overflow-hidden { overflow-y: auto !important; }`. + Use `overflow-x-hidden overflow-y-hidden` if you truly need clipping. +- Touch devices: `packages/ui/src/styles/mobile.css` enforces `min-height: 36px` on `button`. If you build custom segmented controls with ` - ); - })} - - - {/* Content area - mobile drill-down pattern */} -
- {activeTab !== 'settings' && !showPageContent && ( -
- {renderSidebarContent()} -
- )} - - {(activeTab === 'settings' || showPageContent) && ( -
- {renderPageContent()} -
- )} -
- - ); - - return ( - ( -
-
- {showPageContent && ( - - )} - Settings -
- {closeButton} -
- )} - > - {mainContent} -
- ); -}; diff --git a/packages/ui/src/components/sections/agents/AgentsPage.tsx b/packages/ui/src/components/sections/agents/AgentsPage.tsx index cca64647..707ffdd8 100644 --- a/packages/ui/src/components/sections/agents/AgentsPage.tsx +++ b/packages/ui/src/components/sections/agents/AgentsPage.tsx @@ -1,15 +1,16 @@ import React from 'react'; -import { Button } from '@/components/ui/button'; import { ButtonSmall } from '@/components/ui/button-small'; import { Input } from '@/components/ui/input'; +import { NumberInput } from '@/components/ui/number-input'; import { Textarea } from '@/components/ui/textarea'; import { toast } from '@/components/ui'; import { useAgentsStore, type AgentConfig, type AgentScope } from '@/stores/useAgentsStore'; import { useConfigStore } from '@/stores/useConfigStore'; import { usePermissionStore } from '@/stores/permissionStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; +import { useDeviceInfo } from '@/lib/device'; import { opencodeClient } from '@/lib/opencode/client'; -import { RiAddLine, RiAiAgentFill, RiAiAgentLine, RiInformationLine, RiRobot2Line, RiRobotLine, RiSaveLine, RiSubtractLine, RiUser3Line, RiFolderLine } from '@remixicon/react'; +import { RiCloseLine, RiInformationLine, RiRobot2Line, RiSubtractLine, RiUser3Line, RiFolderLine } from '@remixicon/react'; import { cn } from '@/lib/utils'; import { ModelSelector } from './ModelSelector'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; @@ -19,6 +20,7 @@ import { SelectContent, SelectItem, SelectTrigger, + SelectValue, } from '@/components/ui/select'; type PermissionAction = 'allow' | 'ask' | 'deny'; @@ -180,6 +182,7 @@ const buildPermissionConfigWithGlobal = ( export const AgentsPage: React.FC = () => { + const { isMobile } = useDeviceInfo(); const { selectedAgentName, getAgentByName, createAgent, updateAgent, agents, agentDraft, setAgentDraft } = useAgentsStore(); useConfigStore(); @@ -593,604 +596,501 @@ export const AgentsPage: React.FC = () => { return ( -
- {/* Header */} -
-

- {isNewAgent ? 'New Agent' : selectedAgentName} -

-
+
- {} -
-
-

Basic Information

-

- Configure agent identity and behavior mode -

+ {/* Header & Actions */} +
+
+

+ {isNewAgent ? 'New Agent' : selectedAgentName} +

+

+ {isNewAgent ? 'Configure a new assistant persona' : 'Edit agent settings'} +

+
- {isNewAgent && ( -
- -
-
- @ - setDraftName(e.target.value)} - placeholder="agent-name" - className="flex-1 text-foreground placeholder:text-muted-foreground" + {/* Identity & Role */} +
+
+

+ Identity & Role +

+
+ +
+ + {isNewAgent && ( +
+
+ Agent Name +
+
+
+ @ + setDraftName(e.target.value)} + placeholder="agent-name" + className="h-7 w-40 px-2" + /> +
+ +
+
+ )} + +
+ Description +
+