Release v1.01 Enhanced: Vi Control, TUI Gen5, Core Stability
This commit is contained in:
@@ -1,90 +1,231 @@
|
||||
/**
|
||||
* TUI Theme Module - Centralized styling for OpenQode TUI
|
||||
* Provides consistent colors, spacing, and border styles
|
||||
* With capability detection for cross-platform compatibility
|
||||
* TUI Theme Module - Premium Design System
|
||||
* Provides consistent semantic colors, spacing, and rail styling
|
||||
* With profile-gated backgrounds for cross-platform compatibility
|
||||
*
|
||||
* DESIGN PRINCIPLES:
|
||||
* 1. Single accent color (no neon chaos)
|
||||
* 2. Semantic roles (fg, muted, accent, border, success/warn/error)
|
||||
* 3. Profile-gated backgrounds (SAFE_ASCII avoids most backgrounds)
|
||||
* 4. One-frame rule (only outer container has border)
|
||||
*/
|
||||
|
||||
// Capability detection
|
||||
const hasUnicode = process.platform !== 'win32' ||
|
||||
process.env.WT_SESSION || // Windows Terminal
|
||||
process.env.TERM_PROGRAM === 'vscode'; // VS Code integrated terminal
|
||||
import { getCapabilities, PROFILE, isBackgroundOK, isDimOK, isUnicodeOK } from './terminal-profile.mjs';
|
||||
|
||||
// Theme configuration
|
||||
export const theme = {
|
||||
// Spacing scale (terminal rows/chars)
|
||||
spacing: {
|
||||
xs: 0,
|
||||
sm: 1,
|
||||
md: 2,
|
||||
lg: 3
|
||||
},
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SEMANTIC COLOR TOKENS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export const colors = {
|
||||
// Primary text
|
||||
fg: 'white',
|
||||
fgBold: 'whiteBright',
|
||||
|
||||
// Semantic colors
|
||||
colors: {
|
||||
fg: 'white',
|
||||
muted: 'gray',
|
||||
border: 'gray',
|
||||
info: 'cyan',
|
||||
success: 'green',
|
||||
warning: 'yellow',
|
||||
error: 'red',
|
||||
accent: 'magenta',
|
||||
// Muted/secondary text
|
||||
muted: 'gray',
|
||||
mutedDim: 'gray', // isDimOK() ? dimmed gray : regular gray
|
||||
|
||||
// Single accent (not multi-color chaos)
|
||||
accent: 'cyan',
|
||||
accentBold: 'cyanBright',
|
||||
|
||||
// Borders and dividers
|
||||
border: 'gray',
|
||||
borderFocus: 'cyan',
|
||||
divider: 'gray',
|
||||
|
||||
// Semantic status colors
|
||||
success: 'green',
|
||||
warning: 'yellow',
|
||||
error: 'red',
|
||||
info: 'blue',
|
||||
|
||||
// Role-specific rail colors (left rail indicator)
|
||||
rail: {
|
||||
user: 'cyan',
|
||||
assistant: 'white',
|
||||
system: 'yellow'
|
||||
assistant: 'green',
|
||||
system: 'yellow',
|
||||
tool: 'magenta',
|
||||
error: 'red',
|
||||
thinking: 'gray'
|
||||
},
|
||||
|
||||
// Border styles with fallback
|
||||
borders: {
|
||||
default: hasUnicode ? 'round' : 'single',
|
||||
single: 'single',
|
||||
round: hasUnicode ? 'round' : 'single',
|
||||
double: hasUnicode ? 'double' : 'single'
|
||||
// Focus/selection
|
||||
focus: 'cyan',
|
||||
selection: 'blue'
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SPACING SCALE (terminal rows/chars)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export const spacing = {
|
||||
none: 0,
|
||||
xs: 0,
|
||||
sm: 1,
|
||||
md: 2,
|
||||
lg: 3,
|
||||
xl: 4
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// TYPOGRAPHY HIERARCHY
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export const typography = {
|
||||
// Section headers (e.g., "PROJECT", "SESSION")
|
||||
header: { bold: true, color: colors.fg },
|
||||
|
||||
// Labels (e.g., "Branch:", "Model:")
|
||||
label: { color: colors.muted },
|
||||
|
||||
// Values (e.g., "main", "qwen-coder-plus")
|
||||
value: { color: colors.fg },
|
||||
|
||||
// Muted metadata
|
||||
meta: { color: colors.muted, dimColor: true },
|
||||
|
||||
// Status text
|
||||
status: { color: colors.accent },
|
||||
|
||||
// Error text
|
||||
error: { color: colors.error, bold: true }
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// BORDER STYLES
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export function getBorderStyle() {
|
||||
return isUnicodeOK() ? 'round' : 'single';
|
||||
}
|
||||
|
||||
export const borders = {
|
||||
// Only use for outer app frame
|
||||
frame: {
|
||||
style: getBorderStyle,
|
||||
color: colors.border
|
||||
},
|
||||
|
||||
// Card-specific styles
|
||||
cards: {
|
||||
system: {
|
||||
borderStyle: hasUnicode ? 'round' : 'single',
|
||||
borderColor: 'yellow',
|
||||
paddingX: 1,
|
||||
paddingY: 0,
|
||||
marginBottom: 1
|
||||
},
|
||||
user: {
|
||||
marginTop: 1,
|
||||
marginBottom: 1,
|
||||
promptIcon: hasUnicode ? '❯' : '>',
|
||||
promptColor: 'cyan'
|
||||
},
|
||||
assistant: {
|
||||
borderStyle: 'single',
|
||||
borderColor: 'gray',
|
||||
paddingX: 1,
|
||||
paddingY: 0,
|
||||
marginBottom: 1,
|
||||
divider: hasUnicode ? '── Assistant ──' : '-- Assistant --'
|
||||
},
|
||||
error: {
|
||||
borderStyle: hasUnicode ? 'round' : 'single',
|
||||
borderColor: 'red',
|
||||
paddingX: 1,
|
||||
paddingY: 0,
|
||||
marginBottom: 1,
|
||||
icon: hasUnicode ? '⚠' : '!'
|
||||
}
|
||||
// Inner elements use NO borders - only dividers
|
||||
none: null
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// RAIL STYLING (replaces nested boxes)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export const rail = {
|
||||
width: 2, // Rail column width
|
||||
|
||||
// Characters
|
||||
char: {
|
||||
active: isUnicodeOK() ? '│' : '|',
|
||||
streaming: isUnicodeOK() ? '┃' : '|',
|
||||
dimmed: isUnicodeOK() ? '╎' : ':'
|
||||
},
|
||||
|
||||
// Icons with fallback
|
||||
icons: {
|
||||
info: hasUnicode ? 'ℹ' : 'i',
|
||||
warning: hasUnicode ? '⚠' : '!',
|
||||
error: hasUnicode ? '✗' : 'X',
|
||||
success: hasUnicode ? '✓' : 'OK',
|
||||
bullet: hasUnicode ? '•' : '-',
|
||||
arrow: hasUnicode ? '→' : '->',
|
||||
prompt: hasUnicode ? '❯' : '>'
|
||||
// Colors by role
|
||||
colors: {
|
||||
user: colors.rail.user,
|
||||
assistant: colors.rail.assistant,
|
||||
system: colors.rail.system,
|
||||
tool: colors.rail.tool,
|
||||
error: colors.rail.error,
|
||||
thinking: colors.rail.thinking
|
||||
}
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// LAYOUT CONSTANTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export const layout = {
|
||||
// Sidebar
|
||||
sidebar: {
|
||||
minWidth: 20,
|
||||
maxWidth: 28,
|
||||
defaultWidth: 24
|
||||
},
|
||||
|
||||
// Divider between sidebar and main
|
||||
divider: {
|
||||
width: 1,
|
||||
char: isUnicodeOK() ? '│' : '|',
|
||||
color: colors.border
|
||||
},
|
||||
|
||||
// Transcript (main content)
|
||||
transcript: {
|
||||
maxLineWidth: 90, // Clamp for readability
|
||||
minLineWidth: 40,
|
||||
padding: 1
|
||||
},
|
||||
|
||||
// Input bar (fixed height)
|
||||
inputBar: {
|
||||
height: 3, // Fixed - never changes
|
||||
borderTop: true
|
||||
},
|
||||
|
||||
// Status strip (single line)
|
||||
statusStrip: {
|
||||
height: 1
|
||||
},
|
||||
|
||||
// Fixed row reservations
|
||||
reservedRows: {
|
||||
statusStrip: 1,
|
||||
inputBar: 3,
|
||||
frameTop: 1,
|
||||
frameBottom: 1
|
||||
}
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// BREAKPOINTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export const breakpoints = {
|
||||
tiny: 60, // Hide sidebar
|
||||
narrow: 80, // Minimal sidebar
|
||||
medium: 100, // Normal sidebar
|
||||
wide: 120 // Full sidebar
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// PROFILE-GATED BACKGROUNDS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export function getBackground(purpose) {
|
||||
// SAFE_ASCII: avoid backgrounds entirely
|
||||
if (!isBackgroundOK()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const backgrounds = {
|
||||
selection: '#1a1a2e', // Dark selection
|
||||
focus: '#0d1117', // Focus highlight
|
||||
error: '#2d1f1f', // Error background
|
||||
warning: '#2d2a1f', // Warning background
|
||||
thinking: '#1a1a1a' // Thinking block
|
||||
};
|
||||
|
||||
return backgrounds[purpose];
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// UNIFIED THEME OBJECT (backwards compatible)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export const theme = {
|
||||
colors,
|
||||
spacing,
|
||||
typography,
|
||||
borders,
|
||||
rail,
|
||||
layout,
|
||||
breakpoints,
|
||||
|
||||
// Helper functions
|
||||
getBorderStyle,
|
||||
getBackground,
|
||||
|
||||
// Capability checks
|
||||
isUnicodeOK,
|
||||
isBackgroundOK,
|
||||
isDimOK
|
||||
};
|
||||
|
||||
export default theme;
|
||||
|
||||
Reference in New Issue
Block a user