TUICommander — Visual Style Guide
Reference for all UI/CSS/layout work. Every visual change MUST follow this guide.
Design Philosophy
VS Code Dark theme adapted for a terminal-first, developer-focused interface. The UI is a frame for terminal content — chrome recedes, content dominates. No bright whites. Muted UI elements, vivid status colors. Everything monospace except UI labels.
Application Layout
┌─────────────────────────────────────────────────────────────────────┐
│ #toolbar (38px macOS / 32px Win+Linux, --bg-primary, drag region) │
│ ┌──────────────┬────────────────────────────────────┬────────────┐ │
│ │ toolbar-left │ toolbar-center (tab bar) │toolbar-right│ │
│ │ (sidebar w) │ [Tab1] [Tab2] [Tab3] [+] │ [IDE btns] │ │
│ └──────────────┴────────────────────────────────────┴────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ #app-body (flex: 1, flex-direction: row) │
│ ┌──────────┬─────────────────────────────────┬───────────────────┐ │
│ │ #sidebar │ #main │ Side panels │ │
│ │ 300px │ (flex: 1) │ (400px, optional)│ │
│ │ --bg- │ │ │ │
│ │ secondary│ ┌──────────────────────────────┐ │ ┌──────────────┐ │ │
│ │ │ │ #terminal-container │ │ │ Diff or │ │ │
│ │ REPOS │ │ (flex: 1, --bg-primary) │ │ │ Markdown or │ │ │
│ │ section │ │ │ │ │ Notes/Ideas │ │ │
│ │ title │ │ terminal fills this │ │ │ panel │ │ │
│ │ repo │ │ entire area │ │ │ │ │ │
│ │ header │ │ │ │ │ panel-header │ │ │
│ │ branch │ │ │ │ │ panel-content│ │ │
│ │ branch │ │ │ │ │ │ │ │
│ │ │ │ │ │ └──────────────┘ │ │
│ │ FOOTER │ │ │ │ │ │
│ │ [+ Add] │ └──────────────────────────────┘ │ │ │
│ │ [icons] │ │ │ │
│ └──────────┴─────────────────────────────────┴───────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ #status-bar (28px, --bg-secondary, border-top) │
│ [zoom][sessions] [branch ↑2][PR #42][CI ✓] [toggles][💡][⚙][?] │
└─────────────────────────────────────────────────────────────────────┘
Key structural rules:
#appisflex-direction: column, fills 100vh × 100vw.#app-bodyisflex-direction: row,flex: 1,min-height: 0.- Sidebar is fixed-width (resizable 200–500px), main area fills remaining space.
- Side panels (Diff, Markdown, Notes) appear right of
#main, width 400px, max 50vw. - All sections have
overflow: hidden— scrolling is on inner content areas only. - Status bar is always at the bottom, never scrolls.
Color Palette
Values shown are the :root defaults of global.css (the former VS Code Dark palette, in effect until a theme loads). The app supports 13 bundled themes (Commander is the default and the fallback) — all core colors are CSS custom properties overridden at runtime by applyAppTheme() in themes.ts, which also sets html[data-theme="<key>"] so a theme can carry typography rules in global.css (only clean does: neutral gray --tab-*-rgb tints so typed tabs do not each bring their own hue). Font smoothing is global: html forces -webkit-font-smoothing: antialiased and 0.01em tracking, the rasterization every native-looking macOS app uses. When writing CSS, always use variables, never hardcode core palette values.
Theme gallery (dev only). With make dev running, open http://127.0.0.1:1421/theme-gallery.html (src/dev/themeGallery.tsx). It renders one row per bundled theme: the shipped theme on the left and, on the right, the edit proposed in src/dev/themeProposals.ts (a partial override merged over the JSON; empty when nothing is under review). Each card mounts the real Toolbar, Sidebar, TabBar and StatusBar components over stores seeded once in the page (a fake __TAURI_INTERNALS__ in theme-gallery.html answers every invoke), so a component or stylesheet edit repaints every theme through HMR and a theme JSON edit shows up within a second. Only the terminal body and the dialog are stand-ins; never hand-copy component markup into the gallery, a copy drifts within a session (the page polls /__tuic_themes, a serve-only Vite middleware in vite.config.ts). Use it to check a palette change against every theme at once before touching the live app. A static snapshot of the 2026-09-22 review, taken before its edits were applied, is in docs/design/theme-gallery-2026-09-22/. It is not a build input and never ships. To show it as a TUIC ui tab inside a dev build, use http://localhost:1421/...: the dev app is served from 127.0.0.1:1421, and PluginPanel blanks a same-origin iframe (guardSameOriginNav).
CSS Variables (:root in global.css)
| Variable | Default (vscode-dark) | Usage |
|---|---|---|
--bg-primary | #1e1e1e | Main canvas — terminals, panel bodies |
--bg-secondary | #252526 | Sidebar, tab bar, status bar |
--bg-tertiary | #2d2d30 | Inputs, settings rows, button defaults |
--bg-highlight | #37373d | Hover states, active branch bg |
--fg-primary | #cccccc | Primary text (max brightness for text) |
--fg-secondary | #a0a0a0 | Labels, secondary text |
--fg-muted | #9aa1a9 | Section titles, tertiary text |
--accent | #59a8dd | Primary actions, active indicators, links (theme-dependent) |
--accent-hover | #7abde5 | Hover on accent elements (theme-dependent) |
--activity | #59a8dd | Busy/activity pulse indicators (fixed in global.css, not overridden by themes) |
--success | #4ec9b0 | Positive states, open PRs (teal) |
--warning | #dcdcaa | Caution, pending, main branch icon (yellow) |
--attention | #e8984c | Actionable alerts, confirmation prompts (orange) |
--error | #f48771 | Errors, failures, closed PRs (coral) |
--merged | #a371f7 | PR merged badge (purple) |
--unseen | #c084fc | Terminal completed while user wasn’t viewing (purple, clears on view) |
--diff-del | --error mixed 25% with white | Deleted-line counts in the sidebar stats badge. Lighter than --error so a negative diff does not read as an alarm; vscode-light resets it to --error |
--border | #3e3e42 | All borders and dividers |
--text-on-accent | #000000 | Black text on colored badge backgrounds |
--text-on-error | #000000 | Black text on error backgrounds |
--text-on-success | #000000 | Black text on success backgrounds |
Extended Palette (hardcoded, contextual only)
| Color | Context |
|---|---|
#d29922 | Changes requested / review required (orange) |
#e3b341 | CI pending (golden) |
#ffd700 | Rate limit, question icon (gold) |
rgba(122, 162, 247, *) | Branch ahead/behind tint, pulse glow |
rgba(158, 206, 106, *) | Diff additions bg, CI success tint |
rgba(247, 118, 142, *) | Diff deletions bg, CI failure tint |
Background Stacking Order (darkest → lightest)
#1e1e1e --bg-primary Terminal canvas, main area
#252526 --bg-secondary Sidebar, tab bar, status bar, modals
#2d2d30 --bg-tertiary Buttons, inputs, settings rows, panel headers
#37373d --bg-highlight Hover, active branch, selected items
Every surface uses exactly one of these four levels. Elevation = lighter.
Typography
| Variable | Stack | Usage |
|---|---|---|
--font-mono | JetBrains Mono, Fira Code, Hack, Cascadia Code, Source Code Pro, DejaVu Sans Mono, monospace | Terminals, branch names, stats badges, PR badges, code |
--font-ui | -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Liberation Sans, sans-serif | UI labels, buttons, headings, descriptions, settings |
Size Scale
| Variable | Size | Where |
|---|---|---|
--font-3xs | 8px | Micro labels, pixel-level detail |
--font-2xs | 10px | Smallest visible labels |
--font-xs | 11px | Badge text, hotkey hints, metadata |
--font-sm | 12px | Section titles (REPOS), secondary labels |
--font-md | 13px | Branch names, tab names, settings labels — default for UI |
--font-base | 14px | Body text, document default |
--font-lg | 15px | Panel headings |
--font-xl | 17px | Dialog titles |
--font-2xl | 20px | Large headings |
--font-3xl | 24px | Hero text, splash screens |
Font weight: 400 normal, 500 medium (branch names), 600 semibold (repo names, badges), 700 bold (headings only).
Spacing
Fixed Dimensions
| Variable | Value |
|---|---|
--sidebar-width | 300px (resizable: min 200px, max 500px) |
--toolbar-height | 38px macOS / 32px Win+Linux |
--tab-bar-height | 32px |
--status-height | 28px |
Spacing Scale
| Size | Usage |
|---|---|
| 1–2px | Branch item vertical margin, micro separation |
| 4px | Sidebar content top padding, compact flex gaps, micro padding |
| 6px | Icon-to-text gaps, sidebar footer gaps, repo header padding |
| 8px | Button padding, form gaps, sidebar footer padding, standard gap |
| 12px | Branch item horizontal padding, panel header padding, medium padding |
| 16px | Sidebar section margin, branch list left indent, modal padding |
| 20px | Dialog content padding, sidebar empty state padding |
Use gap on flex containers, not margins between children.
Border Radius
| Variable | Value | Usage |
|---|---|---|
--radius-xs | 2px | Minimal — focus rings |
--radius-sm | 3px | Small interactive elements |
--radius-md | 4px | Standard — buttons, badges, inputs, branch items |
--radius-lg | 6px | Larger controls — dropdowns, add-repo button, form inputs |
--radius-xl | 8px | Modals, panels, dialogs |
--radius-pill | 12px | PR badges, status pills |
--radius-full | 50% | Circles — toggle thumbs, repo initials avatar |
Shadows
| Variable | Value | Usage |
|---|---|---|
--shadow-popup | 0 8px 32px rgba(0,0,0,0.4) | Modals, dialogs |
--shadow-dropdown | 0 4px 16px rgba(0,0,0,0.3) | Menus, popovers, context menus |
--shadow-bottom-anchor | 0 -4px 20px rgba(0,0,0,0.4) | Bottom-anchored panels |
Three levels only. Never invent new shadow values.
Transitions & Animation
Durations
| Duration | Usage |
|---|---|
| 0.1s | Hover backgrounds, active states — instant feedback |
| 0.15s | Standard — opacity, color, transform, border changes |
| 0.2s | Layout — sidebar collapse, toggle switches, chevron rotation |
Keyframe Animations
pulse-opacity: Opacity 0.4 → 1.0 → 0.4, infinite. Duration 1.5s or 2s.
Defined in each CSS Module file that uses it (not in global.css — CSS Modules scope animation names).
Used for: active branch icon, CI pending badge, rate limit indicator.
pulse-question: Box-shadow 0 → 0 0 12px 4px rgba(122,162,247,0.4) → 0.
Variants exist with red (error) and orange (confirm) colors.
Used for: terminal tab glow when agent awaits input.
pendulum: Translates text from 0 to -overflow-px and back. Duration computed dynamically from overflow width (~50px/s, minimum 4s cycle). Uses CSS custom properties --overflow-px and --ticker-duration.
Used for: status bar notification text that overflows its container.
Tab status dot color scheme (single ● indicator left of tab name):
- Grey (opacity 0.3): idle — no session or command never ran
- Blue (
--activity, pulse infinite): busy — producing output now - Green (
--success): done — command completed - Purple (
--unseen, static): completed while user wasn’t viewing (clears on view) - Orange (
--attention, pulse infinite): agent needs user input (question) - Red (
--error, pulse infinite): API error or agent stuck
Tab type color scheme (gradient background + colored border-bottom):
- Red (
#ef4444): diff tabs - Blue (
--accent/#7aa2f7): editor tabs - Teal (
#2dd4bf): markdown tabs - Purple (
#a78bfa): panel tabs - Amber (
#fbbf24): remote PTY sessions (created via HTTP/MCP)
Always use ease timing. Respect prefers-reduced-motion. Never transition: all.
Component Reference
Sidebar
#sidebar {
width: var(--sidebar-width); /* 300px */
min-width: 200px;
max-width: 500px;
background: var(--bg-secondary);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
}
Section title (e.g. “REPOS”):
font-size: --font-sm,text-transform: uppercase,color: --fg-mutedletter-spacing: 0.05em,padding: 4px 16px
Repo header:
- Flex row,
gap: 6px,padding: 6px 12px 3px - Repo initials: 28×28px circle,
--accentbg,--text-on-accenttext,--font-xs, semibold - Repo name:
--font-sm, semibold, uppercase,--fg-secondary, truncated with ellipsis - Chevron: the shared
ChevronIcon(src/components/ui/ChevronIcon.tsx, 12px SVG),--fg-secondary, rotates 0→90° on expand (150ms ease). Branch rows use a separate, always-visible disclosure button beside the status icon. The collapsed count stays beside the chevron; the status icon and row only select the branch. - Actions (⋯, +): hidden by default (
opacity: 0), shown on repo-header hover
Branch item (the most complex sidebar element):
.branch-item {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 12px;
border-radius: var(--radius-md);
margin: 1px 0;
transition: background 0.1s;
}
.branch-item:hover { background: var(--bg-highlight); }
.branch-item.active {
background: var(--bg-highlight);
border-left: 2px solid var(--accent);
padding-left: 10px; /* compensate for border */
}
Branch item anatomy (left to right):
[icon 14px + agent count?] [name flex:1] [unmerged mark?] [PR marker?] [diff numbers?] [actions on hover]
- Icon (18px wide, centered):
★yellow for main,Ymuted for feature,Yaccent+pulse when agent active,Ygreen when shell idle,?warning (orange)+pulse when awaiting input - Name:
--font-md, weight 500,--fg-primary, ellipsis on overflow - Agent count (when the tab tree is on): a bare
--font-3xsdigit at the icon’s bottom-right corner with a--bg-secondaryhalo. Never a pill: a pill covers the glyph that tells main, branch and worktree apart - PR marker (optional): a 7px square plus
#N, both in the state color, no fill or border. A conflict rotates the square into a diamond so it does not rely on color. Draft and Review outline the square instead of filling it, as their pills did (Review vs Changes requested differ only by fill). Motion matches the pill form: the conflict marker pulses at 1.5s, Checking and CI Running at 2s; the number never moves. The UI Legend renders this same component The state word (Review, Conflicts, …) and any uncommitted-file count live in thedata-tooltip - Unmerged mark (optional): a 7px outlined square in
--fg-secondary, centered in a 14px target with no pill background. It marks a worktree with commits outside the default branch even when its working tree is clean; hover or focus explains why branch deletion is blocked. - Diff numbers (optional): plain
+N -Nin--font-2xs,--success/--diff-del, no chip. Always shown beside the PR marker, never alternating with it - Actions (on hover only):
max-width: 0 → 44px, two 20×20px buttons (+, ×)
Tab Bar
Located inside #toolbar, center section. Background matches toolbar (--bg-primary).
.tab {
height: var(--tab-bar-height); /* 32px */
padding: 0 12px;
font-size: var(--font-md);
font-family: var(--font-mono);
color: var(--fg-secondary);
background: transparent;
border: none;
border-top: 2px solid transparent;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
min-width: 80px;
max-width: 200px;
}
.tab.active {
color: var(--fg-primary);
border-top-color: var(--accent);
background: var(--bg-secondary);
}
.tab:hover:not(.active) {
color: var(--fg-primary);
background: var(--bg-tertiary);
}
Tab anatomy: [agent badge?] [name, truncated] [close × on hover]
- Agent badge: small colored prefix (e.g.
C claude,G gemini) - Close button: invisible by default,
opacity: 1on tab hover - New tab button
[+]: 28px circle,--accentcolor
Status Bar
.bar {
height: var(--status-height); /* 28px */
min-height: var(--status-height);
background: var(--bg-secondary);
border-top: 1px solid var(--border);
display: flex;
align-items: center;
padding: 0 12px;
font-size: var(--font-sm);
gap: 8px;
overflow: hidden;
}
Three sections: left (zoom, status info, CWD, agent badge, ticker), center (PR + CI badges), right (toggle buttons).
Agent badge (.agentBadge): --font-mono, --font-sm, weight 500, 1px 6px padding, --radius-sm, --bg-tertiary bg. Usage-dependent color classes:
.agentUsage—--fg-secondarytext (normal usage).agentUsageWarning—#dcdcaatext (usage >=70%).agentUsageCritical—#f48771text +pulse-opacityanimation (usage >=90%).agentRateLimited—#f44747text +pulse-opacityanimation
Ticker message (.tickerMessage): --font-mono, --font-sm, --fg-muted, max-width 300px, --radius-sm. Warning priority (>=80): #ffd700 text, gold bg at 0.1 alpha. .tickerClickable adds cursor pointer and hover effect.
Pendulum overflow (.infoTickerActive): When the status info text is wider than its container, a CSS pendulum keyframe animation scrolls the text left then back. Duration is computed dynamically from overflow width (~50px/s). Click dismisses.
Count badges (CountBadge in components/ui): the one counter for every icon button: status bar toggles (errors, ideas, git changes) and the sidebar footer (parked, stale, tunnels). 12px high, --radius-sm, 9px bold tabular digits, top-right corner at -3px of a position: relative button. Color is a tone (accent, error, success), never an inline style. It renders nothing for a zero count.
PR badges (center section): PrBadge + CiBadge components in .githubStatus, separated by a left border. CLOSED PRs are hidden; MERGED PRs have a 5-minute activity-based grace period.
Toggle buttons (right section): --bg-tertiary bg, --border border, --font-xs, 2px 8px padding.
Active: --accent bg, white text. Each has a hotkey hint overlay positioned below.
Side Panels (Diff, Markdown, Notes/Ideas)
All follow the same structure:
.panel {
width: 400px;
min-width: 300px;
max-width: 50vw;
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
background: var(--bg-primary);
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border);
}
.panel-title { font-size: var(--font-lg); font-weight: bold; }
.file-count-badge {
background: var(--accent);
color: var(--text-on-accent);
border-radius: var(--radius-pill);
padding: 1px 6px;
font-size: var(--font-xs);
}
.panel-content { flex: 1; overflow-y: auto; }
Dialog / Modal
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.65);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog {
width: 480px;
max-width: 90vw;
max-height: 80vh;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-popup);
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialog-header {
padding: 16px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.dialog-header h2 { font-size: var(--font-xl); }
.dialog-content { padding: 16px; overflow-y: auto; flex: 1; }
.dialog-actions {
padding: 12px 16px;
display: flex;
justify-content: flex-end;
gap: 8px;
border-top: 1px solid var(--border);
}
Primary button: background: var(--accent); color: var(--text-on-accent); padding: 8px 16px; border-radius: var(--radius-lg);
Secondary button: background: var(--bg-tertiary); color: var(--fg-secondary); same padding/radius.
Danger button: background: var(--error); color: var(--text-on-error);
Form Controls
input, select, textarea {
height: 36px; /* standard height */
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
color: var(--fg-primary);
font-size: var(--font-md);
padding: 0 8px;
}
input:focus, select:focus, textarea:focus {
border-color: var(--accent);
outline: none;
}
Toggle switch: 36×20px track, --radius-full (10px), off=--bg-tertiary, on=--accent. White thumb slides with 0.2s transition.
Range slider: 4px track height, 16px circular thumb in --accent.
Settings Panel
Full-screen overlay. Inner panel: --bg-secondary, 700px max-width, 80vh max-height.
- Left sidebar with tabs (General, Notifications, Dictation, Terminal, Agents)
- Right content area with sections
- Section heading:
<h3>,--font-lg, bold,margin-bottom: 12px - Settings row: flex space-between, label left, control right,
padding: 8px 0
Status Badges Reference
PR State (sidebar .branch-pr-badge)
| State | Background | Border | Text Color | Extra |
|---|---|---|---|---|
| Open | --success | none | --text-on-success | — |
| Draft | transparent | 1px dashed --fg-muted | --fg-muted | — |
| Merged | #a371f7 | none | --text-on-accent | — |
| Closed | --error | none | --text-on-error | — |
| Conflict | --error | none | --text-on-error | pulse-opacity 1.5s |
| CI Failed | --error | none | --text-on-error | bold |
| CI Pending | transparent | 1px solid #e3b341 | #e3b341 | pulse-opacity 2s |
| Changes Req. | #d29922 | none | --text-on-accent | — |
| Review Req. | transparent | 1px solid #d29922 | #d29922 | — |
All badges: font-size: --font-xs, font-family: --font-mono, font-weight: 600, border-radius: --radius-pill, padding: 1px 6px.
CI State (status bar)
| State | Background | Text |
|---|---|---|
| Success | rgba(158,206,106,0.2) | #9ece6a |
| Failure | rgba(247,118,142,0.2) | #f7768e |
| Pending | rgba(224,175,104,0.2) | #e0af68 |
Agent/Usage (tab + status bar)
| State | CSS Class | Style |
|---|---|---|
| Agent running | Tab colored agent prefix | Tab has colored agent prefix badge |
| Usage normal | .agentUsage | --fg-secondary text |
| Usage ≥70% | .agentUsageWarning | #dcdcaa text (warning yellow) |
| Usage ≥90% | .agentUsageCritical | #f48771 text + pulse-opacity 2s |
| Rate limited | .agentRateLimited | #f44747 text + pulse-opacity 2s |
| Ticker warning (≥80 priority) | .tickerWarning | #ffd700 text, gold bg at 0.1 alpha |
| Update available | .updateBadge | #4ec9b0 text, teal bg at 0.15 alpha |
Icons
No icon library dependency. Icons are monochrome inline SVGs with fill="currentColor", so they inherit the surrounding text colour and recolour with the theme for free. Never emoji — AGENTS.md → “Visual”.
Text symbols and Unicode are still correct where the glyph is the content rather than an icon: the branch and state markers in the table below, and single characters inside a dense row. Reach for an inline SVG for anything that reads as an icon.
| Symbol | Meaning | Where |
|---|---|---|
★ | Main/primary branch | Sidebar branch icon |
Y | Feature branch | Sidebar branch icon |
? | Awaiting input | Branch icon (warning/orange, pulsing) |
+ | Add/create | Buttons |
× | Close/remove | Tab close, panel close, dialog close |
⋯ | Context menu | Repo header |
✎ | Edit/rename | Branch double-click |
▶ | Send/execute | Notes panel send button |
ChevronIcon | Chevron (expand/collapse), SVG not text | Sidebar groups, repos, GitHub sections, search bar |
● | Tab status dot | Tab bar (grey=running, green=idle, purple=unseen, blue-pulse=activity, orange-pulse=awaiting, red-pulse=error) |
⎇ | Git branch symbol | Status bar |
💡 | Ideas panel | Status bar, panel header |
Icon dimensions: 18px wide container for branch icons. --font-md or --font-lg size. Always left of text with gap: 6–8px.
Scrollbars
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background: var(--bg-highlight);
border-radius: var(--radius-md);
}
::-webkit-scrollbar-thumb:hover { background: var(--fg-muted); }
Terminal scrollbar overridden to 8px with !important.
Interactive States
Hover
- Background: one level up (
--bg-secondary→--bg-tertiary, or--bg-tertiary→--bg-highlight) - Text:
--fg-secondary→--fg-primary - Border: transparent →
--accent(for add-repo button) - Duration: 0.1s
Active / Selected
- Active branch:
--bg-highlightbg +2px solid var(--accent)left border - Active tab:
--bg-secondarybg +2px solid var(--accent)top border +--fg-primarytext - Active toggle:
--accentbg + white text - No hover animation on already-active items
Focus
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
Excluded on toggle buttons and mic button.
Disabled
opacity: 0.3(strong) or0.5(mild)cursor: not-allowed- No hover, no transitions
Hidden-until-hover
Pattern used for actions that clutter the UI when always visible:
- Repo actions:
opacity: 0; pointer-events: none;→opacity: 1; pointer-events: auto;on.repo-header:hover - Branch actions:
max-width: 0; overflow: hidden;→max-width: 44px;on.branch-item:hover - Tab close button:
opacity: 0→opacity: 1on.tab:hover
Platform Differences
| Property | macOS | Windows/Linux |
|---|---|---|
| Toolbar height | 38px | 32px |
| Traffic light offset | .platform-macos .toolbar-left { padding-left: 78px; } | None |
| System font | -apple-system first | Segoe UI (Win) / Roboto (Linux) first |
| Quit menu | App menu | File menu |
| Check for Updates | App menu | Help menu |
CSS classes on <html>: .platform-macos, .platform-windows, .platform-linux.
Accessibility
- Primary text (#cccccc on #1e1e1e): 10:1+ contrast ratio (exceeds WCAG AAA).
- Secondary text (#a0a0a0 on #252526): 6:1+ (exceeds WCAG AA).
- Status communicated by color + shape + icon — never color alone.
:focus-visibleoutlines for keyboard navigation.prefers-reduced-motionquery disables all animations.- Custom scrollbars maintain 8px touch target.
PWA / Mobile (src/mobile/mobile.css)
The mobile PWA has its own standalone stylesheet at src/mobile/mobile.css, completely independent from the desktop global.css. This allows the mobile UI to evolve separately while sharing the same design language.
What’s shared
- Core color palette (same
--bg-*,--fg-*,--accent,--success,--warning,--attention,--errorvariables with identical default values) - Border radius scale (
--radius-smthrough--radius-full, excluding--radius-xs) - Shadow tokens (
--shadow-popup,--shadow-dropdown) - ANSI terminal palette
What differs
| Property | Desktop (global.css) | Mobile (mobile.css) |
|---|---|---|
| Font mono stack | JetBrains Mono, Fira Code, Hack, Cascadia, … | SF Mono, Menlo, Consolas, DejaVu, … |
| Font size scale | --font-3xs through --font-3xl (8–24px) | Not defined — components use explicit px values |
| Layout variables | --sidebar-width, --toolbar-height, --tab-bar-height, --status-height | Not used — mobile uses flex-based layout |
| User select | Disabled (none) | Enabled (text) |
| Theme variables | --activity, --merged, --unseen | Not present (mobile has no terminal activity tracking yet) |
| Safe areas | Not used | env(safe-area-inset-top/bottom) on #mobile-app |
| Input font-size | From scale | Fixed 16px minimum to prevent iOS auto-zoom |
Keeping in sync
When updating core palette colors in global.css, also update mobile.css — the :root blocks must stay aligned for the shared variables. Theme-specific variables (--activity, --merged, --unseen) are desktop-only and do not need mobile equivalents until that functionality ships in PWA.
Anti-Patterns (DO NOT)
- No bright whites — max text brightness is
--fg-primary(#cccccc). - No new shadows — only the three defined levels exist.
- No
transition: all— always list specific properties. - No hardcoded core colors — use CSS variables for the four bg levels, three fg levels, and status colors.
- No icon libraries, no emoji — monochrome inline SVG with
fill="currentColor". - No off-scale radius — only
--radius-xsthrough--radius-full. - No
!important— except terminal scrollbar overrides. - No pixel values outside the spacing scale unless component-specific dimension (like 28px repo initials).
- No inline styles for theming — all colors and spacing via CSS variables (desktop:
global.css, mobile:mobile.css).
Touch Sidebar Action Trays
Keep branch actions hidden at rest on touch devices. A left swipe reveals a
full-height tray with neutral More, accent Add, and error-coloured Remove
buttons, with at least 44px width per action. Only removable linked worktrees
show Remove. Keep native vertical scrolling with touch-action: pan-y.
Use the existing palette and radius tokens, and disable slide transitions when
reduced motion is requested. Mouse hover controls retain their existing layout.
Rich working agent rows reserve two clamped intent lines. Idle, awaiting-input and error rows stay on one line with status dots; their full intent, task or prompt remains in the tooltip. Compact rows keep their one-line layout.
Rich navigation keeps compact typography and padding. Branch identity and facts share one line, with names truncated and full PR/lifecycle facts in tooltips. Repository metadata disappears when PR and worktree counts are both zero. Header controls use compact sizing to protect repo names. Returned subagents fold into an expandable count independently of running work.
The mobile update banner sits below the app header to keep its small text away
from the translucent iOS status edge. It uses --mobile-update-foreground:
black on Commander’s blue accent and white on Paper’s darker blue. The pressed
state changes the background to --accent-hover instead of fading the whole
strip. Do not reuse Commander’s shared white accent foreground here: at 13px,
its 3.36:1 contrast does not meet WCAG AA.