Skip to content

Commit e1b9496

Browse files
feat: major UI/UX overhaul with git integration, split view, and refined components
Refactors git commands into modular local/remote split, adds AnimatedShow and RemotePollBanner components, improves ChatArea, SplitView, TabBar, Sidebar, ThreadItem, StatusBar, CommandPalette, UsageDashboard, and SettingsOverlay. Updates persistence layer, session management, plugin sandbox, and streaming.
1 parent d09991d commit e1b9496

55 files changed

Lines changed: 5425 additions & 2045 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.impeccable.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## Design Context
2+
3+
### Users
4+
Solo developers and small teams who want a fast, distraction-free AI coding workflow. They're technical, opinionated about their tools, and expect a desktop app that feels native and responsive. They use CodeForge for multi-threaded Claude conversations, code diffs, embedded browsing, and git integration — often with multiple sessions running in parallel.
5+
6+
### Brand Personality
7+
**Modern. Aesthetic. Cool.**
8+
9+
CodeForge should feel like a premium developer tool — the kind of app that makes you want to use it. It's confident without being loud, polished without being precious. Think high-end IDE meets sleek productivity tool.
10+
11+
### Aesthetic Direction
12+
- **Sleek and professional** — clean lines, deliberate spacing, restrained use of color
13+
- **Dark-first**: Obsidian Forge theme is the flagship experience (near-black backgrounds, blue-indigo accents, purple undertones)
14+
- **Never playful** — no rounded bubbly shapes, no whimsical illustrations, no emoji-driven UI, no pastel palettes
15+
- **Technical confidence** — monospace accents (JetBrains Mono), semantic status colors, information-dense where appropriate
16+
- **Subtle polish** — micro-interactions, smooth transitions (0.12-0.15s), glow effects on focus — felt but not flashy
17+
- **No anti-references specified**, but avoid anything that reads as consumer/casual (Notion-cute, Slack-playful, VS Code-cluttered)
18+
19+
### Design Principles
20+
21+
1. **Substance over decoration** — Every visual element should serve a purpose. No ornamental flourishes. If it doesn't help the developer, remove it.
22+
23+
2. **Calm intensity** — The interface should feel powerful and capable without being overwhelming. Dark backgrounds, controlled contrast, deliberate whitespace. High information density when needed, breathing room when not.
24+
25+
3. **Seamless confidence** — Interactions should feel inevitable — smooth transitions, predictable layouts, no jank. The app should feel like it was built by someone who cares about craft.
26+
27+
4. **Dark-native design** — Design for dark mode first. Light theme is supported but secondary. Color choices, contrast ratios, and glow effects should be optimized for the dark experience.
28+
29+
5. **Developer-grade precision** — Pixel-perfect alignment, consistent spacing scale, monospace where data matters. Developers notice when things are off — nothing should be.
30+
31+
### Design System Reference
32+
33+
- **Colors**: CSS variables in `frontend/src/styles/global.css`, theme JSONs in `crates/tauri-app/themes/`
34+
- **Typography**: DM Sans (body), JetBrains Mono (code/technical), loaded via Google Fonts in `index.html`
35+
- **Spacing**: No formal tokens — derived scale: 2/4/6/8/10/12/16/20/24/40px
36+
- **Radii**: `--radius-sm` (6px), `--radius-md` (10px), `--radius-lg` (14px), `--radius-xl` (20px), `--radius-pill` (100px)
37+
- **Borders**: Translucent white overlays (`rgba(255,255,255, 0.06-0.10)`)
38+
- **Shadows**: Deep layered shadows on modals, minimal elsewhere
39+
- **Transitions**: 0.12-0.15s ease for interactions, 0.2s cubic-bezier for overlays
40+
- **Icons**: Inline SVGs, stroke-based (2-2.5px), 14-16px
41+
- **Components**: Self-contained with inline `<style>` tags, all referencing CSS custom properties

.vite/deps/_metadata.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

.vite/deps/package.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/app/src/subscriptions/agent.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::message::{AgentMessage, Message};
1111
/// Shared state holding event receivers for active sessions.
1212
#[derive(Debug, Clone, Default)]
1313
pub struct AgentEventReceivers {
14-
inner: Arc<Mutex<HashMap<SessionId, mpsc::UnboundedReceiver<AgentEvent>>>>,
14+
inner: Arc<Mutex<HashMap<SessionId, mpsc::Receiver<AgentEvent>>>>,
1515
}
1616

1717
// We use a constant hash since there is only ever one subscription instance.
@@ -28,7 +28,7 @@ impl AgentEventReceivers {
2828
}
2929
}
3030

31-
pub async fn insert(&self, id: SessionId, rx: mpsc::UnboundedReceiver<AgentEvent>) {
31+
pub async fn insert(&self, id: SessionId, rx: mpsc::Receiver<AgentEvent>) {
3232
self.inner.lock().await.insert(id, rx);
3333
}
3434

crates/core/src/color.rs

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -340,36 +340,19 @@ pub struct ThemePalette {
340340
impl ThemePalette {
341341
/// Generate CSS custom property declarations for this palette.
342342
pub fn to_css_variables(&self) -> String {
343-
let mut css = String::new();
344-
css.push_str(&format!(" --color-primary: {};\n", self.primary.to_hex()));
345-
css.push_str(&format!(
346-
" --color-secondary: {};\n",
347-
self.secondary.to_hex()
348-
));
349-
css.push_str(&format!(
350-
" --color-background: {};\n",
351-
self.background.to_hex()
352-
));
353-
css.push_str(&format!(
354-
" --color-surface: {};\n",
355-
self.surface.to_hex()
356-
));
357-
css.push_str(&format!(" --color-text: {};\n", self.text.to_hex()));
358-
css.push_str(&format!(
359-
" --color-text-muted: {};\n",
360-
self.text_muted.to_hex()
361-
));
362-
css.push_str(&format!(" --color-border: {};\n", self.border.to_hex()));
363-
css.push_str(&format!(
364-
" --color-success: {};\n",
365-
self.success.to_hex()
366-
));
367-
css.push_str(&format!(
368-
" --color-warning: {};\n",
369-
self.warning.to_hex()
370-
));
371-
css.push_str(&format!(" --color-error: {};\n", self.error.to_hex()));
372-
css.push_str(&format!(" --color-info: {};\n", self.info.to_hex()));
343+
use std::fmt::Write;
344+
let mut css = String::with_capacity(512);
345+
let _ = writeln!(css, " --color-primary: {};", self.primary.to_hex());
346+
let _ = writeln!(css, " --color-secondary: {};", self.secondary.to_hex());
347+
let _ = writeln!(css, " --color-background: {};", self.background.to_hex());
348+
let _ = writeln!(css, " --color-surface: {};", self.surface.to_hex());
349+
let _ = writeln!(css, " --color-text: {};", self.text.to_hex());
350+
let _ = writeln!(css, " --color-text-muted: {};", self.text_muted.to_hex());
351+
let _ = writeln!(css, " --color-border: {};", self.border.to_hex());
352+
let _ = writeln!(css, " --color-success: {};", self.success.to_hex());
353+
let _ = writeln!(css, " --color-warning: {};", self.warning.to_hex());
354+
let _ = writeln!(css, " --color-error: {};", self.error.to_hex());
355+
let _ = writeln!(css, " --color-info: {};", self.info.to_hex());
373356
css
374357
}
375358

crates/core/src/version.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,13 @@ impl VersionReq {
329329

330330
impl fmt::Display for VersionReq {
331331
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332-
let parts: Vec<String> = self.constraints.iter().map(|c| c.to_string()).collect();
333-
write!(f, "{}", parts.join(", "))
332+
for (i, c) in self.constraints.iter().enumerate() {
333+
if i > 0 {
334+
write!(f, ", ")?;
335+
}
336+
write!(f, "{c}")?;
337+
}
338+
Ok(())
334339
}
335340
}
336341

crates/git/src/blame.rs

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
use chrono::{DateTime, Utc};
77
use serde::{Deserialize, Serialize};
8-
use std::collections::HashMap;
8+
use std::collections::{HashMap, HashSet};
99
use std::fmt;
1010

1111
/// Attribution data for a single line in a blame result.
@@ -139,43 +139,39 @@ impl BlameResult {
139139
}
140140

141141
/// Return the unique commit hashes that contributed to this file.
142-
pub fn unique_commits(&self) -> Vec<String> {
143-
let mut commits: Vec<String> = self
144-
.lines
142+
pub fn unique_commits(&self) -> Vec<&str> {
143+
self.lines
145144
.iter()
146-
.map(|l| l.commit_hash.clone())
147-
.collect();
148-
commits.sort();
149-
commits.dedup();
150-
commits
145+
.map(|l| l.commit_hash.as_str())
146+
.collect::<HashSet<_>>()
147+
.into_iter()
148+
.collect()
151149
}
152150

153151
/// Return the unique authors that contributed to this file.
154-
pub fn unique_authors(&self) -> Vec<String> {
155-
let mut authors: Vec<String> = self
156-
.lines
152+
pub fn unique_authors(&self) -> Vec<&str> {
153+
self.lines
157154
.iter()
158-
.map(|l| l.author.clone())
159-
.collect();
160-
authors.sort();
161-
authors.dedup();
162-
authors
155+
.map(|l| l.author.as_str())
156+
.collect::<HashSet<_>>()
157+
.into_iter()
158+
.collect()
163159
}
164160

165161
/// Return a per-author line count breakdown.
166-
pub fn author_line_counts(&self) -> HashMap<String, usize> {
162+
pub fn author_line_counts(&self) -> HashMap<&str, usize> {
167163
let mut counts = HashMap::new();
168164
for line in &self.lines {
169-
*counts.entry(line.author.clone()).or_insert(0) += 1;
165+
*counts.entry(line.author.as_str()).or_insert(0) += 1;
170166
}
171167
counts
172168
}
173169

174170
/// Return a per-commit line count breakdown.
175-
pub fn commit_line_counts(&self) -> HashMap<String, usize> {
171+
pub fn commit_line_counts(&self) -> HashMap<&str, usize> {
176172
let mut counts = HashMap::new();
177173
for line in &self.lines {
178-
*counts.entry(line.commit_hash.clone()).or_insert(0) += 1;
174+
*counts.entry(line.commit_hash.as_str()).or_insert(0) += 1;
179175
}
180176
counts
181177
}
@@ -187,32 +183,34 @@ impl BlameResult {
187183
return groups;
188184
}
189185

190-
let mut current_hash = self.lines[0].commit_hash.clone();
186+
let mut group_start_idx: usize = 0;
191187
let mut start_line = 1;
192188
let mut count = 1;
193189

194190
for (i, line) in self.lines.iter().enumerate().skip(1) {
195-
if line.commit_hash == current_hash {
191+
if line.commit_hash == self.lines[group_start_idx].commit_hash {
196192
count += 1;
197193
} else {
194+
let prev = &self.lines[group_start_idx];
198195
groups.push(BlameGroup {
199-
commit_hash: current_hash.clone(),
200-
author: self.lines[i - 1].author.clone(),
196+
commit_hash: prev.commit_hash.clone(),
197+
author: prev.author.clone(),
201198
date: self.lines[i - 1].date,
202199
start_line,
203200
line_count: count,
204201
});
205-
current_hash = line.commit_hash.clone();
202+
group_start_idx = i;
206203
start_line = line.line_number;
207204
count = 1;
208205
}
209206
}
210207

211208
// Push the last group.
209+
let first = &self.lines[group_start_idx];
212210
if let Some(last) = self.lines.last() {
213211
groups.push(BlameGroup {
214-
commit_hash: current_hash,
215-
author: last.author.clone(),
212+
commit_hash: first.commit_hash.clone(),
213+
author: first.author.clone(),
216214
date: last.date,
217215
start_line,
218216
line_count: count,
@@ -300,7 +298,7 @@ impl BlameSummary {
300298
let (primary_author, primary_lines) = author_counts
301299
.iter()
302300
.max_by_key(|(_, count)| *count)
303-
.map(|(author, count)| (Some(author.clone()), *count))
301+
.map(|(author, count)| (Some((*author).to_owned()), *count))
304302
.unwrap_or((None, 0));
305303

306304
Self {

crates/persistence/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version.workspace = true
44
edition.workspace = true
55

66
[dependencies]
7+
codeforge-core = { path = "../core" }
78
rusqlite = { version = "0.31", features = ["bundled"] }
89
serde = { workspace = true }
910
uuid = { workspace = true }

crates/persistence/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ pub mod migrations;
33
pub mod models;
44
pub mod queries;
55

6+
pub use codeforge_core::id::{MessageId, ProjectId, SessionId, ThreadId};
67
pub use db::Database;
78
pub use models::*;

0 commit comments

Comments
 (0)