Comprehensive plan to elevate echoes-code's UI/UX by adopting proven patterns from t3code. Tracked in PROGRESS.md
- Create
src/renderer/src/components/ui/ErrorBoundary.tsx - Class component with
componentDidCatchandgetDerivedStateFromError - Error UI: gradient backdrop, error message, collapsible stack trace, "Reload" button
- Theme-aware styling using CSS variables
- Wrap app root in
App.tsx(or future router root) - Reference: t3code
__root.tsxerror boundary
- Install
@base-ui-components/react(unstyled primitives) - Create
src/renderer/src/components/ui/Toast.tsx- ToastProvider wrapping app root
- Toast types: success, error, info, warning, loading
- Stacking with auto-dismiss (5s default)
- Position: bottom-right
- Entrance/exit animations (slideUp + fadeIn)
- Manual dismiss button
-
useToast()hook for imperative usage:toast.success("Saved")
- Create
src/renderer/src/hooks/useToast.ts - Replace inline notifications:
- PR creation success/error (TitleBar.tsx)
- Feedback submission result (TitleBar.tsx)
- Provider connection status changes (App.tsx)
- Authentication errors (SettingsView.tsx)
- Settings save confirmation (SettingsView.tsx)
- Rate limit warnings (ThreadView.tsx)
- Reference: t3code
toast.tsx(400+ lines, stacking, swipe-to-dismiss)
- Add
.no-transitionsclass toglobal.css:.no-transitions, .no-transitions * { transition: none !important; animation: none !important; }
- Update theme switch handler to:
- Add
.no-transitionstodocumentElement - Apply
data-theme requestAnimationFrame→ remove.no-transitions
- Add
- Reference: t3code
useTheme.tstransition suppression
- Create
src/renderer/src/stores/draftStore.ts- Simple store:
Map<threadId, string> -
setDraft(threadId, text),getDraft(threadId),clearDraft(threadId) - Persist to
localStoragekeyechoes-drafts
- Simple store:
- Integrate into ThreadView.tsx:
- On thread switch: save current input, load draft for new thread
- On send: clear draft
- Reference: t3code
useComposerDraftStore
Create src/renderer/src/components/ui/ with these primitives:
-
Button.tsx
- Variants: default, outline, ghost, destructive, link
- Sizes: sm, default, lg
- Loading state (spinner + disabled)
- Icon-only variant
- Focus-visible ring
-
Input.tsx
- Sizes: sm, default, lg
- Focus ring with accent color
- Disabled state (opacity 64%)
- Placeholder styling
- Error state (red border)
-
Badge.tsx
- Variants: default, success, warning, error, outline
- Replace inline status dots, "Needs input" labels, cloud/local badges
-
Dialog.tsx
- Overlay + centered panel
- Header, body, footer sections
- Close button (X icon)
- Escape to close, click-outside to close
- Entrance animation (scale + opacity)
- Replace: About dialog, Feedback dialog, Delete confirmation, Settings modal
-
Tooltip.tsx
- Positioned floating text
- Configurable side/alignment
- Delay on hover (200ms)
- Replace: PR error tooltip, shortcut hints
-
Spinner.tsx
- Standardized loading spinner
- Sizes: sm, default, lg
- Replace all duplicated inline SVG spinners
-
Separator.tsx
- Horizontal/vertical variants
- Replace inline
borderTopdividers
-
Skeleton.tsx
- Shimmer animation (2s linear infinite, 120deg gradient)
- Rounded rectangle base
- Width/height props
- Reference: t3code
skeleton.tsx
-
ScrollArea.tsx
- Custom scrollbar wrapper
- Theme-aware scrollbar colors
- 6px width, hover brightens
-
Kbd.tsx
- Keyboard shortcut display badge
- Styled key caps
- Reference: t3code
kbd.tsx
- Install
zustand - Create stores in
src/renderer/src/stores/:-
appStore.ts— activeThreadId, expandedProjects, settingsOpen, sidebarOpen -
settingsStore.ts— theme, accent, fontSize, density, language, permissionLevel, planMode, useWorktree (persisted to localStorage) -
draftStore.ts— per-thread draft messages (from Phase 1) -
providerStore.ts— provider connection status, accounts
-
- Migrate state out of App.tsx into stores
- Remove prop drilling — components access stores directly
- Reference: t3code
store.tswith Zustand 5.0, persisted state, versioned migration
- Apply Skeleton component (from 2.1) to:
- Sidebar: skeleton project/thread list while Convex query loads
- ThreadView: skeleton message bubbles while messages load
- SettingsView: skeleton sections while settings load
- User profile: skeleton avatar + name while user loads
- Add contextual empty states:
- "Select a thread or create a new one"
- "No messages yet"
- "No projects — create your first project"
- Reference: t3code
skeleton.tsx+ empty state patterns
- Icon-only buttons: Add
aria-labelto all icon-only buttons- Close, minimize, maximize (TitleBar)
- Menu, delete, expand/collapse (Sidebar)
- Copy, send, stop (ThreadView)
- Custom widgets: Add proper ARIA roles
- Theme selector:
role="radiogroup"+role="radio"+aria-checked - Accent selector: same pattern
- Density selector: same pattern
- Tab lists:
role="tablist"+role="tab"+aria-selected
- Theme selector:
- Form inputs:
aria-invalidon validation errors,aria-describedbyfor help text - Live regions:
aria-live="polite"on streaming message area - Spinner:
role="status"+aria-label="Loading" - Focus management:
- Visible focus rings:
outline: 2px solid var(--accent)on:focus-visible - Logical tab order across TitleBar -> Sidebar -> Content -> Input
- Focus trap in modals/dialogs
- Return focus to trigger when modal closes
- Visible focus rings:
- Keyboard navigation:
- Arrow keys in sidebar project/thread list
- Escape closes modals/menus/panels
- Reference: t3code ARIA attributes, focus-visible rings, role usage
- Create
src/renderer/src/components/ui/Popover.tsx- Auto-positioning (flips when near viewport edges)
- Configurable side (top/right/bottom/left) and alignment
- Focus management
- Click-outside dismiss
- Smooth entrance transition
- Replace existing absolute-positioned menus:
- TitleBar menu dropdown
- Model selector dropdown (ThreadView)
- Permission level selector (ThreadView)
- Reference: t3code
popover.tsx
- Update
global.cssscrollbar styles per theme:[data-theme="dark"] ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); } [data-theme="light"] ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); } [data-theme="midnight"] ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); } [data-theme="nord"] ::-webkit-scrollbar-thumb { background: rgba(136,192,208,0.25); } [data-theme="sepia"] ::-webkit-scrollbar-thumb { background: rgba(160,82,45,0.3); }
- Add hover state: slightly brighter on
::-webkit-scrollbar-thumb:hover - Reduce width to 6px
- Reference: t3code scrollbar styling per theme
- Install
tailwindcss4.x,@tailwindcss/vite - Configure
@themein CSS to map existing CSS variables to Tailwind tokens - Install
class-variance-authority(CVA) for component variants - Migrate UI primitives (Phase 2 components) to Tailwind classes
- Migrate existing components one-by-one:
- EchoesLogo.tsx
- ContextMenu.tsx
- TitleBar.tsx
- Sidebar.tsx
- ThreadView.tsx
- SettingsView.tsx
- LoginPage.tsx
- MarkdownContent.tsx
- GitHubPanel.tsx
- Remove inline
const styles: Record<string, React.CSSProperties>blocks - Reference: t3code Tailwind 4.0 + CVA setup
- Install
@tanstack/react-router - Configure hash-history for Electron
- Define route tree:
routes/ __root.tsx — providers, error boundary, toast _app.tsx — authenticated layout (TitleBar + Sidebar + outlet) _app.index.tsx — empty state / onboarding _app.$threadId.tsx — ThreadView _app.settings.tsx — SettingsView login.tsx — LoginPage - Split App.tsx (1560 lines) into route files
- Move route-level state into URL (activeThreadId, settingsView)
- Reference: t3code
@tanstack/react-routerfile-based routes
- Create
src/renderer/src/components/ui/Sheet.tsx- Slide-in panels from any edge (left/right/top/bottom)
- Backdrop overlay with click-to-close
- Smooth transition (300ms cubic-bezier)
keepMountedoption- Portal rendering for z-index
- Use for:
- Mobile sidebar (slide from left)
- GitHub panel (slide from right)
- Future terminal drawer (slide from bottom)
- Reference: t3code
sheet.tsx
- Create
src/renderer/src/hooks/useMediaQuery.ts - Define breakpoints: mobile (<768px), tablet (768-1180px), desktop (>1180px)
- Responsive sidebar:
- Desktop: fixed 280px
- Mobile: offcanvas overlay via Sheet, hamburger toggle
- Responsive settings:
- Desktop: modal dialog
- Mobile: full-screen view
- Touch targets: minimum 44x44px on pointer-coarse devices
- GitHub panel: collapse to icon on narrow widths
- Reference: t3code
useMediaQuery.ts, mobile breakpoint patterns
- Install
shiki(or@shikijs/core) - Create highlighting utility with:
- LRU cache (500 entries)
- Lazy language grammar loading
- Suspense boundary with plain-text fallback
- Theme-aware token colors (one theme per app theme)
- Integrate into
MarkdownContent.tsxcode block renderer - Reference: t3code
ChatMarkdown.tsxShiki + LRU cache
- Define type scale as CSS variables:
--font-xs: 0.75rem; --font-sm: 0.875rem; --font-base: 1rem; --font-lg: 1.125rem; --font-xl: 1.25rem;
- Standardize line heights: 1.5 body, 1.25 headings, 1.6 code
- Apply consistently across all components
- Reference: t3code DM Sans + systematic font scale
- Install
lexical+@lexical/react+ plugins - Create
src/renderer/src/components/ComposerEditor.tsx- @mentions for files/symbols (with icons)
- History plugin (Ctrl+Z/Y in input)
- Slash commands (/plan, /diff, /model)
- Keyboard: Shift+Enter newline, Ctrl+Enter send
- Keep: image paste support
- Replace textarea in ThreadView
- Reference: t3code
ComposerPromptEditor.tsxwith Lexical
- Install
xterm+xterm-addon-fit - Create
src/renderer/src/components/TerminalDrawer.tsx- Slides up from bottom of ThreadView
- Resizable height (drag handle)
- Toggle via Ctrl+` keyboard shortcut
- Shows stdout/stderr from executed commands
- Connects to backend PTY
- Create terminal state store
- Reference: t3code
ThreadTerminalDrawer.tsx+ xterm.js
- Create
src/renderer/src/components/DiffPanel.tsx- Side panel (right of ThreadView), collapsible
- Per-message file diffs with Shiki syntax highlighting
- Split (side-by-side) and unified views
- File tree with changed file list
- Resizable width via CSS variable
- Desktop: inline sidebar (min 26rem)
- Mobile: Sheet modal
- Reference: t3code
DiffPanel.tsx+DiffWorkerPoolProvider
- Create
src/renderer/src/components/CommandPalette.tsx- Open with Ctrl+K / Cmd+K
- Searchable list of app actions
- Recent items section
- Keyboard navigation (arrow keys + enter)
- Fuzzy matching
- Register actions:
- New thread, switch thread, open settings
- Switch model, toggle plan mode
- Toggle sidebar, toggle GitHub panel
- Reference: t3code
command.tsx
- 9 components, 4 hooks, 1 CSS file
- Inline React styles (
const styles: Record<string, React.CSSProperties>) - No component library, no router, no state management library
- App.tsx is 1560 lines with all routing + state
- No toast, no error boundary, no skeleton loading
- ~25+ components (9 existing + 12 UI primitives + power features)
- Tailwind CSS with CVA for variant management
- Tanstack Router with file-based routes
- Zustand stores for state management
- Toast system, error boundaries, skeleton loading
- Rich text editor, terminal, diff panel, command palette
- Accessible, responsive, theme-polished
@base-ui-components/react — unstyled UI primitives
zustand — state management
@tanstack/react-router — file-based routing
tailwindcss — utility CSS (v4)
@tailwindcss/vite — Vite plugin
class-variance-authority — component variants
shiki — syntax highlighting
lexical @lexical/react — rich text editor
xterm xterm-addon-fit — terminal emulator