docs(agent): streamline guidance and skills

Keep always-on instructions concise and route specialized work through focused skills. Split large skills into progressive references and add dedicated change, desktop, sync, and performance guidance.
This commit is contained in:
Bohdan Triapitsyn
2026-07-14 00:45:44 +03:00
parent b36afbf5ee
commit 68f1c1efe3
20 changed files with 1257 additions and 1371 deletions
+55 -325
View File
@@ -1,350 +1,80 @@
---
name: theme-system
description: Use when creating or modifying UI components, styling, visual elements, or icons in OpenChamber. All UI colors must use theme tokens - never hardcoded values or Tailwind color classes. All icons must use the shared Icon component from the SVG sprite system - never import from @remixicon/react directly.
license: MIT
compatibility: opencode
description: Use when creating or modifying OpenChamber UI components, styling, colors, buttons, visual states, themes, or icons.
---
## Overview
# Theme System
OpenChamber uses a JSON-based theme system. Themes are defined in `packages/ui/src/lib/theme/themes/`. Users can also add custom themes via `~/.config/openchamber/themes/`.
## Core Rules
**Core principle:** UI colors must use theme tokens - never hardcoded hex colors or Tailwind color classes.
- Use semantic OpenChamber theme tokens; never hardcode hex colors or generic Tailwind palette colors.
- Use shared UI primitives before introducing feature-local controls.
- Use the shared `Button`; do not create button wrappers such as `ButtonSmall` or `ButtonLarge`.
- Use the sprite-based `Icon`; never import icons directly from `@remixicon/react`.
- Apply hover tokens only to interactive elements.
- Use status colors only for actual status/feedback.
- Use selection tokens for selected state and primary tokens for primary actions.
## When to Use
## Load References By Task
- Creating or modifying UI components
- Working with colors, backgrounds, borders, or text
- **Working with icons — adding, changing, or creating icon usages**
| Task | Required reference |
|---|---|
| Choosing colors/tokens or reviewing styled examples | `references/tokens-and-examples.md` |
| Adding, converting, storing, or generating icons | `references/icons.md` |
| Adding built-in or custom themes | `references/adding-themes.md` |
## Quick Decision Tree
Load every matching reference before editing. Settings work must also load `settings-ui-patterns`; user-facing or accessible text must load `locale-ui-patterns`.
1. **Code display?** → `syntax.*`
2. **Feedback/status?** → `status.*`
3. **Primary CTA?** → `primary.*`
4. **Interactive/clickable?** → `interactive.*`
5. **Background layer?** → `surface.*`
6. **Text?** → `surface.foreground` or `surface.mutedForeground`
## Token Decision
## Critical Rules
1. Code display -> `syntax.*`
2. Error/warning/success/info -> `status.*`
3. Primary CTA -> `primary.*`
4. Hover/pressed/focus -> `interactive.*`
5. Selected/active state -> `interactive.selection*`
6. Background/text/border layer -> `surface.*` and semantic utility classes
- `surface.elevated` = inputs, cards, panels
- `interactive.hover` = **ONLY on clickable elements**
- `interactive.selection` = active/selected states (not primary!)
- Status colors = **ONLY for actual feedback** (errors, warnings, success)
- Input footers = `bg-transparent` on elevated background
Prefer CSS variables/classes for component styling. Use `useThemeSystem()` only when an API requires resolved color values.
## Button Rules (MANDATORY)
## Button Contract
Use only the shared `Button` component from `packages/ui/src/components/ui/button.tsx`.
Use `Button` from `packages/ui/src/components/ui/button.tsx`.
- Do not create wrapper button components (for example `ButtonLarge`, `ButtonSmall`).
- Do not hardcode button height/padding classes when a `size` variant exists.
- Use semantic button variants consistently; avoid ad-hoc one-off button styling.
| Variant | Use |
|---|---|
| `default` | Primary local action |
| `outline` | Visible secondary action |
| `secondary` | Soft secondary action |
| `ghost` | Quiet row/toolbar action |
| `destructive` | Destructive action |
| `chip` | Compact selectable option with `aria-pressed` |
| `link` | Rare inline text action |
### Allowed Button Variants
| Size | Use |
|---|---|
| `xs` | Dense row/list control |
| `sm` | Compact action |
| `default` | Standard action |
| `lg` | Prominent action |
| `icon` | Icon-only square action |
| Variant | Use for | Token direction |
|-------|-------|-------|
| `default` | Primary action in a local section/dialog | `primary.*` |
| `outline` | Secondary visible action | `surface.elevated` + `interactive.*` |
| `secondary` | Soft secondary action | `interactive.hover` / `interactive.active` |
| `ghost` | Low-emphasis row/toolbar action | transparent + `interactive.hover` |
| `destructive` | Destructive actions (`Delete`, `Revert all`) | `status.error*` |
| `link` | Rare inline text action only | text-link style |
Do not hardcode button height/padding when a size variant exists. Do not recreate selection/destructive styling with ad-hoc classes.
### Allowed Button Sizes
| Size | Use for |
|------|---------|
| `xs` | Dense controls in rows/lists |
| `sm` | Default compact action buttons |
| `default` | Standard form/page actions |
| `lg` | Prominent large actions |
| `icon` | Icon-only square button |
### Button Selection Quick Guide
1. Main CTA in section/dialog -> `default`
2. Side action next to CTA -> `outline`
3. Quiet auxiliary action -> `ghost`
4. Dangerous action -> `destructive`
5. Tiny row action -> keep same variant, set `size="xs"`
### Never Use
- Hardcoded hex colors (`#FF0000`)
- Tailwind colors (`bg-white`, `text-blue-500`, `bg-gray-*`)
- Deprecated: `bg-secondary`, `bg-muted`
## Usage
### Via Hook
```tsx
import { useThemeSystem } from '@/contexts/useThemeSystem';
const { currentTheme } = useThemeSystem();
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
```
### Via CSS Variables
```tsx
<div className="bg-[var(--surface-elevated)] hover:bg-[var(--interactive-hover)]">
```
## Color Tokens
### Surface Colors
| Token | Usage |
|-------|-------|
| `surface.background` | Main app background |
| `surface.elevated` | Inputs, cards, panels, popovers |
| `surface.muted` | Secondary backgrounds, sidebars |
| `surface.foreground` | Primary text |
| `surface.mutedForeground` | Secondary text, hints |
| `surface.subtle` | Subtle dividers |
### Interactive Colors
| Token | Usage |
|-------|-------|
| `interactive.border` | Default borders |
| `interactive.hover` | Hover on **clickable elements only** |
| `interactive.selection` | Active/selected items |
| `interactive.selectionForeground` | Text on selection |
| `interactive.focusRing` | Focus indicators |
### Status Colors
| Token | Usage |
|-------|-------|
| `status.error` | Errors, validation failures |
| `status.warning` | Warnings, cautions |
| `status.success` | Success messages |
| `status.info` | Informational messages |
Each has variants: `*`, `*Foreground`, `*Background`, `*Border`.
### Primary Colors
| Token | Usage |
|-------|-------|
| `primary.base` | Primary CTA buttons |
| `primary.hover` | Hover on primary elements |
| `primary.foreground` | Text on primary background |
**Primary vs Selection:** Primary = "click me" (CTA), Selection = "currently active" (state).
### Syntax Colors
For code display only. Never use for UI elements.
| Token | Usage |
|-------|-------|
| `syntax.base.background` | Code block background |
| `syntax.base.foreground` | Default code text |
| `syntax.base.keyword` | Keywords |
| `syntax.base.string` | Strings |
| `syntax.highlights.diffAdded` | Added lines |
| `syntax.highlights.diffRemoved` | Removed lines |
## Examples
### Input Area
## Icon Contract
```tsx
const { currentTheme } = useThemeSystem();
import { Icon } from '@/components/icon/Icon';
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
<textarea className="bg-transparent" />
<div className="bg-transparent">{/* Footer - transparent! */}</div>
</div>
<Icon name="check" className="size-4" />
```
### Active Tab
Use `IconName` for icon values stored in arrays, objects, state, or config. `Icon` has no `size` prop. Run `bun run icons:generate` when introducing a sprite name, and never edit `sprite.ts` manually. Load `references/icons.md` for the complete workflow.
```tsx
<button className={isActive
? 'bg-interactive-selection text-interactive-selection-foreground'
: 'hover:bg-interactive-hover/50'
}>
```
## Verification
### Error Message
```tsx
<div style={{
color: currentTheme.colors.status.error,
backgroundColor: currentTheme.colors.status.errorBackground
}}>
```
### Card
```tsx
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
<h3 style={{ color: currentTheme.colors.surface.foreground }}>Title</h3>
<p style={{ color: currentTheme.colors.surface.mutedForeground }}>Description</p>
</div>
```
## Icon System (MANDATORY)
OpenChamber uses an SVG sprite-based icon system. **Never import from `@remixicon/react`.** Always use the shared `Icon` component.
### Import
```tsx
import { Icon } from "@/components/icon/Icon";
import type { IconName } from "@/components/icon/icons";
```
### Usage
```tsx
<Icon name="arrow-down-s" className="h-4 w-4" />
<Icon name="loader-4" className="size-4 animate-spin" />
```
### Naming Convention
Convert Remixicon component names to kebab-case sprite names:
1. Strip `Ri` prefix
2. Strip `Line` suffix
3. Convert PascalCase to kebab-case
4. Lowercase everything
| Remixicon | Sprite name |
|-----------|-------------|
| `RiArrowDownSLine` | `arrow-down-s` |
| `RiCheckLine` | `check` |
| `RiLoader4Line` | `loader-4` |
| `RiGithubFill` | `github-fill` |
| `RiBrainAi3Line` | `brain-ai-3` |
### Fill Variants
For filled (solid) icon variants, append `-fill` explicitly. The generator tries `Line` suffix first, then `Fill`, then bare name.
```tsx
<Icon name="github-fill" /> {/* RiGithubFill */}
<Icon name="github" /> {/* RiGithubLine (default) */}
```
### Sizing
The `Icon` component has **no `size` prop**. Use Tailwind classes:
```tsx
<Icon name="check" className="h-4 w-4" /> {/* 16px - most common */}
<Icon name="check" className="size-5" /> {/* 20px */}
<Icon name="check" className="h-3 w-3" /> {/* 12px */}
```
### Adding a New Icon (Workflow)
**In order:**
1. Use the icon in code with the correct kebab-case name:
```tsx
<Icon name="new-icon-name" className="h-4 w-4" />
```
2. If used as a value (not JSX), use `IconName` type:
```tsx
const config = { icon: "new-icon-name" as const };
```
3. Regenerate the sprite:
```bash
bun run icons:generate
```
4. The script scans all source files, reverse-maps to Remixicon names, extracts SVG paths, and regenerates `sprite.ts`.
5. Verify: `bun run type-check`
**Do NOT manually edit `sprite.ts`.** Always regenerate.
### Type Safety for Icon Values
When icons are stored in objects/arrays, change the type from `ComponentType` to `IconName` and render via `<Icon name={value} />`:
```tsx
// ❌ Old: component reference
const items = [{ icon: RiStackLine }];
return <items[0].icon className="h-4 w-4" />;
// ✅ New: IconName string
import type { IconName } from "@/components/icon/icons";
const items: { icon: IconName }[] = [{ icon: "stack" }];
return <Icon name={items[0].icon} className="h-4 w-4" />;
```
## Wrong vs Right
### Wrong
```tsx
// ❌ Importing from @remixicon/react
import { RiArrowDownSLine } from "@remixicon/react";
<RiArrowDownSLine className="h-4 w-4" />
// ❌ Hardcoded colors
<div style={{ backgroundColor: '#F2F0E5' }}>
<button className="bg-blue-500">
// Primary for active tab
<Tab className="bg-primary">Active</Tab>
// Hover on static element
<div className="hover:bg-interactive-hover">Static card</div>
// Colored footer on input
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
<textarea />
<div style={{ backgroundColor: currentTheme.colors.surface.muted }}>Footer</div>
</div>
```
### Right
```tsx
// ✅ Using the Icon component
import { Icon } from "@/components/icon/Icon";
<Icon name="arrow-down-s" className="h-4 w-4" />
// Theme tokens
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
<button style={{ backgroundColor: currentTheme.colors.primary.base }}>
// Selection for active tab
<Tab style={{ backgroundColor: currentTheme.colors.interactive.selection }}>Active</Tab>
// Hover only on clickable
<button className="hover:bg-[var(--interactive-hover)]">Click</button>
// Transparent footer
<div style={{ backgroundColor: currentTheme.colors.surface.elevated }}>
<textarea className="bg-transparent" />
<div className="bg-transparent">Footer</div>
</div>
```
## References
- **[Adding Themes](references/adding-themes.md)** - Built-in and custom themes
## Key Files
- Theme types: `packages/ui/src/types/theme.ts`
- Theme hook: `packages/ui/src/contexts/useThemeSystem.ts`
- CSS generator: `packages/ui/src/lib/theme/cssGenerator.ts`
- Built-in themes: `packages/ui/src/lib/theme/themes/`
- Icon component: `packages/ui/src/components/icon/Icon.tsx`
- Icon sprite data: `packages/ui/src/components/icon/sprite.ts` (auto-generated)
- Icon types: `packages/ui/src/components/icon/icons.ts`
- Icon sprite generator: `scripts/generate-icon-sprite.mjs`
- Icon docs: `packages/ui/src/components/icon/README.md`
- No hardcoded/palette colors were introduced.
- Buttons use shared variants and sizes.
- Icons use `Icon`/`IconName`, and generated sprite changes are intentional.
- Hover, selection, primary, and status semantics are distinct.
- Light/dark/high-contrast and long-text states remain legible.
- Relevant type-check, visual/runtime validation, and generated-asset checks ran.
@@ -0,0 +1,59 @@
# Icon System
## Contract
Use `Icon` from `@/components/icon/Icon` and `IconName` from `@/components/icon/icons`. Do not import icon components directly from `@remixicon/react`.
```tsx
import { Icon } from '@/components/icon/Icon';
import type { IconName } from '@/components/icon/icons';
<Icon name="arrow-down-s" className="size-4" />
```
`Icon` has no `size` prop. Size it with classes.
## Naming
Convert Remixicon names to sprite names:
1. Remove `Ri`.
2. Remove the `Line` suffix.
3. Convert PascalCase to lowercase kebab-case.
4. Preserve filled variants with explicit `-fill`.
| Remixicon | Sprite name |
|---|---|
| `RiArrowDownSLine` | `arrow-down-s` |
| `RiCheckLine` | `check` |
| `RiLoader4Line` | `loader-4` |
| `RiGithubFill` | `github-fill` |
## Config Values
Store icon names, not component references:
```tsx
const items: Array<{ icon: IconName }> = [{ icon: 'stack' }];
return <Icon name={items[0].icon} className="size-4" />;
```
Use literal inference (`as const`) only when the surrounding type does not already provide `IconName`.
## Adding An Icon
1. Use the correct kebab-case name in source.
2. Type non-JSX values as `IconName`.
3. Run `bun run icons:generate`.
4. Inspect generated changes and run relevant type-check/build validation.
Never edit `packages/ui/src/components/icon/sprite.ts` manually. The generator scans source usages, maps names to Remixicon, and regenerates the sprite.
## Key Files
- Component: `packages/ui/src/components/icon/Icon.tsx`
- Types: `packages/ui/src/components/icon/icons.ts`
- Generated sprite: `packages/ui/src/components/icon/sprite.ts`
- Generator: `scripts/generate-icon-sprite.mjs`
- Documentation: `packages/ui/src/components/icon/README.md`
@@ -0,0 +1,112 @@
# Theme Tokens And Examples
## Token Families
### Surface
| Token | Usage |
|---|---|
| `surface.background` | Main app background |
| `surface.elevated` | Inputs, cards, panels, popovers |
| `surface.muted` | Secondary backgrounds and sidebars |
| `surface.foreground` | Primary text |
| `surface.mutedForeground` | Secondary text and hints |
| `surface.subtle` | Subtle dividers |
### Interactive
| Token | Usage |
|---|---|
| `interactive.border` | Default borders |
| `interactive.hover` | Hover on clickable elements only |
| `interactive.active` | Pressed interaction state |
| `interactive.selection` | Active/selected items |
| `interactive.selectionForeground` | Text on selection |
| `interactive.focusRing` | Focus indicators |
### Status
Use status colors only for actual feedback.
- `status.error`: errors and validation failures
- `status.warning`: cautions
- `status.success`: successful outcomes
- `status.info`: informational feedback
Each family may expose foreground, background, and border variants.
### Primary
- `primary.base`: primary CTA
- `primary.hover`: primary hover
- `primary.foreground`: content on primary
Primary means “act”; selection means “currently active.” Do not use primary to mark ordinary selected tabs or rows.
### Syntax
Use `syntax.*` only for code display: code backgrounds/text, keywords, strings, and diff highlights. Never use syntax colors for ordinary UI chrome.
## Usage
Prefer semantic utility classes when available:
```tsx
<div className="bg-[var(--surface-elevated)] text-foreground" />
<button className="hover:bg-interactive-hover" />
```
Use `useThemeSystem()` when a library/API requires actual color values:
```tsx
const { currentTheme } = useThemeSystem();
<Chart color={currentTheme.colors.status.error} />
```
## Common Patterns
### Input Area
```tsx
<div className="bg-[var(--surface-elevated)]">
<textarea className="bg-transparent" />
<div className="bg-transparent">...</div>
</div>
```
Input footers stay transparent over the elevated input surface.
### Active Item
```tsx
<button className={isActive
? 'bg-interactive-selection text-interactive-selection-foreground'
: 'hover:bg-interactive-hover'
} />
```
### Error Feedback
```tsx
<div className="bg-[var(--status-error-background)] text-[var(--status-error-foreground)]" />
```
### Neutral Card
```tsx
<section className="bg-[var(--surface-elevated)] text-foreground">
<p className="text-muted-foreground">...</p>
</section>
```
## Wrong Patterns
```tsx
<div style={{ backgroundColor: '#F2F0E5' }} />
<button className="bg-blue-500" />
<div className="hover:bg-interactive-hover">Static content</div>
<Tab className="bg-primary">Active</Tab>
```
Use theme tokens, apply hover only to interactive elements, and distinguish selection from primary actions.